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 * @see_also:
25 *
26 * This is a network sink that uses libcurl.
27 *
28 */
29
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33
34 #include "gstcurlbasesink.h"
35 #include "gstcurlsshsink.h"
36
37 #include <curl/curl.h>
38 #include <string.h>
39 #include <stdio.h>
40
41 #ifdef G_OS_WIN32
42 #include <winsock2.h>
43 #else
44 #include <sys/socket.h>
45 #include <netinet/in.h>
46 #include <netinet/ip.h>
47 #include <netinet/tcp.h>
48 #endif
49 #include <sys/types.h>
50 #include <unistd.h>
51 #include <sys/stat.h>
52 #include <fcntl.h>
53
54 /* Default values */
55 #define GST_CAT_DEFAULT gst_curl_ssh_sink_debug
56
57 /* Plugin specific settings */
58
59 GST_DEBUG_CATEGORY_STATIC (gst_curl_ssh_sink_debug);
60
61 enum
62 {
63 PROP_0,
64 PROP_SSH_AUTH_TYPE,
65 PROP_SSH_PUB_KEYFILE,
66 PROP_SSH_PRIV_KEYFILE,
67 PROP_SSH_KEY_PASSPHRASE,
68 PROP_SSH_KNOWNHOSTS,
69 PROP_SSH_HOST_PUBLIC_KEY_MD5,
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_DEBUG_OBJECT (klass, "class_init");
125
126 gst_element_class_set_static_metadata (element_class,
127 "Curl SSH sink", "Sink/Network",
128 "Upload data over SSH/SFTP using libcurl", "Sorin L. <sorin@axis.com>");
129
130 gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_curl_ssh_sink_finalize);
131
132 gobject_class->set_property = gst_curl_ssh_sink_set_property;
133 gobject_class->get_property = gst_curl_ssh_sink_get_property;
134
135 klass->set_options_unlocked = gst_curl_ssh_sink_set_options_unlocked;
136
137 g_object_class_install_property (gobject_class, PROP_SSH_AUTH_TYPE,
138 g_param_spec_enum ("ssh-auth-type", "SSH authentication type",
139 "SSH authentication method to authenticate on the SSH/SFTP server",
140 GST_TYPE_CURL_SSH_SINK_AUTH_TYPE, GST_CURLSSH_AUTH_NONE,
141 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
142
143 g_object_class_install_property (gobject_class, PROP_SSH_PUB_KEYFILE,
144 g_param_spec_string ("ssh-pub-keyfile",
145 "SSH public key file",
146 "The complete path & filename of the SSH public key file",
147 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
148
149 g_object_class_install_property (gobject_class, PROP_SSH_PRIV_KEYFILE,
150 g_param_spec_string ("ssh-priv-keyfile",
151 "SSH private key file",
152 "The complete path & filename of the SSH private key file",
153 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
154
155 g_object_class_install_property (gobject_class, PROP_SSH_KEY_PASSPHRASE,
156 g_param_spec_string ("ssh-key-passphrase", "Passphrase of the priv key",
157 "The passphrase used to protect the SSH private key file",
158 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
159
160 g_object_class_install_property (gobject_class, PROP_SSH_KNOWNHOSTS,
161 g_param_spec_string ("ssh-knownhosts",
162 "SSH known hosts",
163 "The complete path & filename of the SSH 'known_hosts' file",
164 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
165
166 g_object_class_install_property (gobject_class, PROP_SSH_HOST_PUBLIC_KEY_MD5,
167 g_param_spec_string ("ssh-host-pubkey-md5",
168 "MD5 checksum of the remote host's public key",
169 "MD5 checksum (32 hexadecimal digits, case-insensitive) of the "
170 "remote host's public key",
171 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
172
173 g_object_class_install_property (gobject_class, PROP_SSH_ACCEPT_UNKNOWNHOST,
174 g_param_spec_boolean ("ssh-accept-unknownhost",
175 "SSH accept unknown host",
176 "Accept an unknown remote public host key",
177 FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
178 }
179
180 static void
gst_curl_ssh_sink_init(GstCurlSshSink * sink)181 gst_curl_ssh_sink_init (GstCurlSshSink * sink)
182 {
183 sink->ssh_auth_type = GST_CURLSSH_AUTH_NONE;
184 sink->ssh_pub_keyfile = NULL;
185 sink->ssh_priv_keyfile = NULL;
186 sink->ssh_key_passphrase = NULL;
187 sink->ssh_knownhosts = NULL;
188 sink->ssh_host_public_key_md5 = NULL;
189 sink->ssh_accept_unknownhost = FALSE;
190 }
191
192 static void
gst_curl_ssh_sink_finalize(GObject * gobject)193 gst_curl_ssh_sink_finalize (GObject * gobject)
194 {
195 GstCurlSshSink *this = GST_CURL_SSH_SINK (gobject);
196
197 GST_DEBUG ("finalizing curlsshsink");
198
199 g_free (this->ssh_pub_keyfile);
200 g_free (this->ssh_priv_keyfile);
201 g_free (this->ssh_key_passphrase);
202 g_free (this->ssh_knownhosts);
203 g_free (this->ssh_host_public_key_md5);
204
205 G_OBJECT_CLASS (parent_class)->finalize (gobject);
206 }
207
208 static void
gst_curl_ssh_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)209 gst_curl_ssh_sink_set_property (GObject * object, guint prop_id,
210 const GValue * value, GParamSpec * pspec)
211 {
212 GstCurlSshSink *sink;
213 GstState cur_state;
214
215 g_return_if_fail (GST_IS_CURL_SSH_SINK (object));
216 sink = GST_CURL_SSH_SINK (object);
217
218 gst_element_get_state (GST_ELEMENT (sink), &cur_state, NULL, 0);
219
220 if (cur_state == GST_STATE_PLAYING || cur_state == GST_STATE_PAUSED) {
221 return;
222 }
223
224 GST_OBJECT_LOCK (sink);
225 switch (prop_id) {
226 case PROP_SSH_AUTH_TYPE:
227 sink->ssh_auth_type = g_value_get_enum (value);
228 GST_DEBUG_OBJECT (sink, "ssh_auth_type set to %d", sink->ssh_auth_type);
229 break;
230
231 case PROP_SSH_PUB_KEYFILE:
232 g_free (sink->ssh_pub_keyfile);
233 sink->ssh_pub_keyfile = g_value_dup_string (value);
234 GST_DEBUG_OBJECT (sink, "ssh_pub_keyfile set to %s",
235 sink->ssh_pub_keyfile);
236 break;
237
238 case PROP_SSH_PRIV_KEYFILE:
239 g_free (sink->ssh_priv_keyfile);
240 sink->ssh_priv_keyfile = g_value_dup_string (value);
241 GST_DEBUG_OBJECT (sink, "ssh_priv_keyfile set to %s",
242 sink->ssh_priv_keyfile);
243 break;
244
245 case PROP_SSH_KEY_PASSPHRASE:
246 g_free (sink->ssh_key_passphrase);
247 sink->ssh_key_passphrase = g_value_dup_string (value);
248 GST_DEBUG_OBJECT (sink, "ssh_key_passphrase set to %s",
249 sink->ssh_key_passphrase);
250 break;
251
252 case PROP_SSH_KNOWNHOSTS:
253 g_free (sink->ssh_knownhosts);
254 sink->ssh_knownhosts = g_value_dup_string (value);
255 GST_DEBUG_OBJECT (sink, "ssh_knownhosts set to %s", sink->ssh_knownhosts);
256 break;
257
258 case PROP_SSH_HOST_PUBLIC_KEY_MD5:
259 g_free (sink->ssh_host_public_key_md5);
260 sink->ssh_host_public_key_md5 = g_value_dup_string (value);
261 GST_DEBUG_OBJECT (sink, "ssh_host_public_key_md5 set to %s",
262 sink->ssh_host_public_key_md5);
263 break;
264
265 case PROP_SSH_ACCEPT_UNKNOWNHOST:
266 sink->ssh_accept_unknownhost = g_value_get_boolean (value);
267 GST_DEBUG_OBJECT (sink, "ssh_accept_unknownhost set to %d",
268 sink->ssh_accept_unknownhost);
269 break;
270
271 default:
272 GST_DEBUG_OBJECT (sink, "invalid property id %d", prop_id);
273 break;
274 }
275 GST_OBJECT_UNLOCK (sink);
276 }
277
278 static void
gst_curl_ssh_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)279 gst_curl_ssh_sink_get_property (GObject * object, guint prop_id,
280 GValue * value, GParamSpec * pspec)
281 {
282 GstCurlSshSink *sink;
283
284 g_return_if_fail (GST_IS_CURL_SSH_SINK (object));
285 sink = GST_CURL_SSH_SINK (object);
286
287 switch (prop_id) {
288 case PROP_SSH_AUTH_TYPE:
289 g_value_set_enum (value, sink->ssh_auth_type);
290 break;
291
292 case PROP_SSH_PUB_KEYFILE:
293 g_value_set_string (value, sink->ssh_pub_keyfile);
294 break;
295
296 case PROP_SSH_PRIV_KEYFILE:
297 g_value_set_string (value, sink->ssh_priv_keyfile);
298 break;
299
300 case PROP_SSH_KEY_PASSPHRASE:
301 g_value_set_string (value, sink->ssh_key_passphrase);
302 break;
303
304 case PROP_SSH_KNOWNHOSTS:
305 g_value_set_string (value, sink->ssh_knownhosts);
306 break;
307
308 case PROP_SSH_HOST_PUBLIC_KEY_MD5:
309 g_value_set_string (value, sink->ssh_host_public_key_md5);
310 break;
311
312 case PROP_SSH_ACCEPT_UNKNOWNHOST:
313 g_value_set_boolean (value, sink->ssh_accept_unknownhost);
314 break;
315
316 default:
317 GST_DEBUG_OBJECT (sink, "invalid property id");
318 break;
319 }
320 }
321
322 static gboolean
gst_curl_ssh_sink_set_options_unlocked(GstCurlBaseSink * bcsink)323 gst_curl_ssh_sink_set_options_unlocked (GstCurlBaseSink * bcsink)
324 {
325 GstCurlSshSink *sink = GST_CURL_SSH_SINK (bcsink);
326 CURLcode curl_err = CURLE_OK;
327
328 /* set SSH specific options here */
329 if (sink->ssh_pub_keyfile) {
330 if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_PUBLIC_KEYFILE,
331 sink->ssh_pub_keyfile)) != CURLE_OK) {
332 bcsink->error = g_strdup_printf ("failed to set public key file: %s",
333 curl_easy_strerror (curl_err));
334 return FALSE;
335 }
336 }
337
338 if (sink->ssh_priv_keyfile) {
339 if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_PRIVATE_KEYFILE,
340 sink->ssh_priv_keyfile)) != CURLE_OK) {
341 bcsink->error = g_strdup_printf ("failed to set private key file: %s",
342 curl_easy_strerror (curl_err));
343 return FALSE;
344 }
345 }
346
347 if (sink->ssh_knownhosts) {
348 if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_KNOWNHOSTS,
349 sink->ssh_knownhosts)) != CURLE_OK) {
350 bcsink->error = g_strdup_printf ("failed to set known_hosts file: %s",
351 curl_easy_strerror (curl_err));
352 return FALSE;
353 }
354 }
355
356 if (sink->ssh_host_public_key_md5) {
357 /* libcurl is freaking tricky. If the input string is not exactly 32
358 * hexdigits long it silently ignores CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 and
359 * performs the transfer without authenticating the server! */
360 if (strlen (sink->ssh_host_public_key_md5) != 32) {
361 bcsink->error = g_strdup ("MD5-hash string has invalid length, "
362 "must be exactly 32 hexdigits!");
363 return FALSE;
364 }
365
366 if ((curl_err =
367 curl_easy_setopt (bcsink->curl, CURLOPT_SSH_HOST_PUBLIC_KEY_MD5,
368 sink->ssh_host_public_key_md5)) != CURLE_OK) {
369 bcsink->error = g_strdup_printf ("failed to set remote host's public "
370 "key MD5: %s", curl_easy_strerror (curl_err));
371 return FALSE;
372 }
373 }
374
375 /* make sure we only accept PASSWORD or PUBLICKEY auth methods
376 * (can be extended later) */
377 if (sink->ssh_auth_type == GST_CURLSSH_AUTH_PASSWORD ||
378 sink->ssh_auth_type == GST_CURLSSH_AUTH_PUBLICKEY) {
379
380 /* set the SSH_AUTH_TYPE */
381 if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_AUTH_TYPES,
382 sink->ssh_auth_type)) != CURLE_OK) {
383 bcsink->error = g_strdup_printf ("failed to set authentication type: %s",
384 curl_easy_strerror (curl_err));
385 return FALSE;
386 }
387
388 /* if key authentication -> provide the private key passphrase as well */
389 if (sink->ssh_auth_type == GST_CURLSSH_AUTH_PUBLICKEY) {
390 if (sink->ssh_key_passphrase) {
391 if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_KEYPASSWD,
392 sink->ssh_key_passphrase)) != CURLE_OK) {
393 bcsink->error = g_strdup_printf ("failed to set private key "
394 "passphrase: %s", curl_easy_strerror (curl_err));
395 return FALSE;
396 }
397 } else {
398 /* The user did not provide the passphrase for the private key.
399 * This can still be a valid situation, if (s)he chose not to
400 * protect the private key with a passphrase - but not recommended! */
401 GST_WARNING_OBJECT (sink, "Warning: key authentication chosen but "
402 "'ssh-key-passphrase' not provided!");
403 }
404 }
405
406 } else {
407 bcsink->error = g_strdup_printf ("Error: unsupported authentication type: "
408 "%d.", sink->ssh_auth_type);
409 return FALSE;
410 }
411
412 /* Install the SSH_KEYFUNCTION callback...
413 * IMPORTANT: this callback gets called only if CURLOPT_SSH_KNOWNHOSTS
414 * is also set! */
415 if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_KEYFUNCTION,
416 curl_ssh_sink_sshkey_cb)) != CURLE_OK) {
417 bcsink->error = g_strdup_printf ("failed to set SSH_KEYFUNCTION callback: "
418 "%s", curl_easy_strerror (curl_err));
419 return FALSE;
420 }
421 /* SSH_KEYFUNCTION callback successfully installed so go on and
422 * set the '*clientp' parameter as well */
423 if ((curl_err =
424 curl_easy_setopt (bcsink->curl, CURLOPT_SSH_KEYDATA,
425 sink)) != CURLE_OK) {
426 bcsink->error = g_strdup_printf ("failed to set CURLOPT_SSH_KEYDATA: %s",
427 curl_easy_strerror (curl_err));
428 return FALSE;
429 }
430
431 return TRUE;
432 }
433
434
435 /* A 'curl_sshkey_cb' callback function. It gets called when the known_host
436 * matching has been done, to allow the application to act and decide for
437 * libcurl how to proceed.
438 * The callback will only be called if CURLOPT_SSH_KNOWNHOSTS is also set!
439 * NOTE:
440 * * use CURLOPT_SSH_KEYFUNCTION to install the callback func
441 * * use CURLOPT_SSH_KEYDATA to pass in the actual "*clientp"
442 */
443 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)444 curl_ssh_sink_sshkey_cb (CURL * easy_handle, /* easy handle */
445 const struct curl_khkey *knownkey, /* known - key from known_hosts */
446 const struct curl_khkey *foundkey, /* found - key from remote end */
447 enum curl_khmatch match, /* libcurl's view on the keys */
448 void *clientp)
449 {
450 GstCurlSshSink *sink = (GstCurlSshSink *) clientp;
451
452 /* the default action to be taken upon pub key matching */
453 guint res_action = CURLKHSTAT_REJECT;
454
455 switch (match) {
456 case CURLKHMATCH_OK:
457 res_action = CURLKHSTAT_FINE;
458 GST_INFO_OBJECT (sink,
459 "Remote public host key is matching known_hosts, OK to proceed.");
460 break;
461
462 case CURLKHMATCH_MISMATCH:
463 GST_WARNING_OBJECT (sink,
464 "Remote public host key mismatch in known_hosts, aborting "
465 "connection.");
466 /* Reject the connection. The old mismatching key has to be manually
467 * removed from 'known_hosts' before being able to connect again to
468 * the respective host. */
469 break;
470
471 case CURLKHMATCH_MISSING:
472 GST_OBJECT_LOCK (sink);
473 if (sink->ssh_accept_unknownhost == TRUE) {
474 /* the key was not found in known_hosts but the user chose to
475 * accept it */
476 res_action = CURLKHSTAT_FINE_ADD_TO_FILE;
477 GST_INFO_OBJECT (sink, "Accepting and adding new public host key to "
478 "known_hosts.");
479 } else {
480 /* the key was not found in known_hosts and the user chose not
481 * to accept connections to unknown hosts */
482 GST_WARNING_OBJECT (sink,
483 "Remote public host key is unknown, rejecting connection.");
484 }
485 GST_OBJECT_UNLOCK (sink);
486 break;
487
488 default:
489 /* something went wrong, we got some bogus key match result */
490 GST_CURL_BASE_SINK (sink)->error =
491 g_strdup ("libcurl internal error during known_host matching");
492 break;
493 }
494
495 return res_action;
496 }
497