FFmpeg  4.0
libssh.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2013 Lukasz Marek <lukasz.m.luki@gmail.com>
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 #include <fcntl.h>
22 #define LIBSSH_STATIC
23 #include <libssh/sftp.h>
24 #include "libavutil/avstring.h"
25 #include "libavutil/opt.h"
26 #include "libavutil/attributes.h"
27 #include "libavformat/avio.h"
28 #include "avformat.h"
29 #include "internal.h"
30 #include "url.h"
31 
32 typedef struct {
33  const AVClass *class;
34  ssh_session session;
35  sftp_session sftp;
36  sftp_file file;
37  sftp_dir dir;
38  int64_t filesize;
40  int trunc;
41  char *priv_key;
43 
44 static av_cold int libssh_create_ssh_session(LIBSSHContext *libssh, const char* hostname, unsigned int port)
45 {
46  static const int verbosity = SSH_LOG_NOLOG;
47 
48  if (!(libssh->session = ssh_new())) {
49  av_log(libssh, AV_LOG_ERROR, "SSH session creation failed: %s\n", ssh_get_error(libssh->session));
50  return AVERROR(ENOMEM);
51  }
52  ssh_options_set(libssh->session, SSH_OPTIONS_HOST, hostname);
53  ssh_options_set(libssh->session, SSH_OPTIONS_PORT, &port);
54  ssh_options_set(libssh->session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity);
55  if (libssh->rw_timeout > 0) {
56  long timeout = libssh->rw_timeout * 1000;
57  ssh_options_set(libssh->session, SSH_OPTIONS_TIMEOUT_USEC, &timeout);
58  }
59 
60  if (ssh_options_parse_config(libssh->session, NULL) < 0) {
61  av_log(libssh, AV_LOG_WARNING, "Could not parse the config file.\n");
62  }
63 
64  if (ssh_connect(libssh->session) != SSH_OK) {
65  av_log(libssh, AV_LOG_ERROR, "Connection failed: %s\n", ssh_get_error(libssh->session));
66  return AVERROR(EIO);
67  }
68 
69  return 0;
70 }
71 
72 static av_cold int libssh_authentication(LIBSSHContext *libssh, const char *user, const char *password)
73 {
74  int authorized = 0;
75  int auth_methods;
76 
77  if (user)
78  ssh_options_set(libssh->session, SSH_OPTIONS_USER, user);
79 
80  if (ssh_userauth_none(libssh->session, NULL) == SSH_AUTH_SUCCESS)
81  return 0;
82 
83  auth_methods = ssh_userauth_list(libssh->session, NULL);
84 
85  if (auth_methods & SSH_AUTH_METHOD_PUBLICKEY) {
86  if (libssh->priv_key) {
87  ssh_string pub_key;
88  ssh_private_key priv_key;
89  int type;
90  if (!ssh_try_publickey_from_file(libssh->session, libssh->priv_key, &pub_key, &type)) {
91  priv_key = privatekey_from_file(libssh->session, libssh->priv_key, type, password);
92  if (ssh_userauth_pubkey(libssh->session, NULL, pub_key, priv_key) == SSH_AUTH_SUCCESS) {
93  av_log(libssh, AV_LOG_DEBUG, "Authentication successful with selected private key.\n");
94  authorized = 1;
95  }
96  } else {
97  av_log(libssh, AV_LOG_DEBUG, "Invalid key is provided.\n");
98  return AVERROR(EACCES);
99  }
100  } else if (ssh_userauth_autopubkey(libssh->session, password) == SSH_AUTH_SUCCESS) {
101  av_log(libssh, AV_LOG_DEBUG, "Authentication successful with auto selected key.\n");
102  authorized = 1;
103  }
104  }
105 
106  if (!authorized && password && (auth_methods & SSH_AUTH_METHOD_PASSWORD)) {
107  if (ssh_userauth_password(libssh->session, NULL, password) == SSH_AUTH_SUCCESS) {
108  av_log(libssh, AV_LOG_DEBUG, "Authentication successful with password.\n");
109  authorized = 1;
110  }
111  }
112 
113  if (!authorized) {
114  av_log(libssh, AV_LOG_ERROR, "Authentication failed.\n");
115  return AVERROR(EACCES);
116  }
117 
118  return 0;
119 }
120 
122 {
123  if (!(libssh->sftp = sftp_new(libssh->session))) {
124  av_log(libssh, AV_LOG_ERROR, "SFTP session creation failed: %s\n", ssh_get_error(libssh->session));
125  return AVERROR(ENOMEM);
126  }
127 
128  if (sftp_init(libssh->sftp) != SSH_OK) {
129  av_log(libssh, AV_LOG_ERROR, "Error initializing sftp session: %s\n", ssh_get_error(libssh->session));
130  return AVERROR(EIO);
131  }
132 
133  return 0;
134 }
135 
136 static av_cold int libssh_open_file(LIBSSHContext *libssh, int flags, const char *file)
137 {
138  int access;
139 
140  if ((flags & AVIO_FLAG_WRITE) && (flags & AVIO_FLAG_READ)) {
141  access = O_CREAT | O_RDWR;
142  if (libssh->trunc)
143  access |= O_TRUNC;
144  } else if (flags & AVIO_FLAG_WRITE) {
145  access = O_CREAT | O_WRONLY;
146  if (libssh->trunc)
147  access |= O_TRUNC;
148  } else
149  access = O_RDONLY;
150 
151  /* 0666 = -rw-rw-rw- = read+write for everyone, minus umask */
152  if (!(libssh->file = sftp_open(libssh->sftp, file, access, 0666))) {
153  av_log(libssh, AV_LOG_ERROR, "Error opening sftp file: %s\n", ssh_get_error(libssh->session));
154  return AVERROR(EIO);
155  }
156 
157  return 0;
158 }
159 
161 {
162  sftp_attributes stat;
163 
164  if (!(stat = sftp_fstat(libssh->file))) {
165  av_log(libssh, AV_LOG_WARNING, "Cannot stat remote file.\n");
166  libssh->filesize = -1;
167  } else {
168  libssh->filesize = stat->size;
169  sftp_attributes_free(stat);
170  }
171 }
172 
174 {
175  LIBSSHContext *libssh = h->priv_data;
176  if (libssh->file) {
177  sftp_close(libssh->file);
178  libssh->file = NULL;
179  }
180  if (libssh->sftp) {
181  sftp_free(libssh->sftp);
182  libssh->sftp = NULL;
183  }
184  if (libssh->session) {
185  ssh_disconnect(libssh->session);
186  ssh_free(libssh->session);
187  libssh->session = NULL;
188  }
189  return 0;
190 }
191 
192 static av_cold int libssh_connect(URLContext *h, const char *url, char *path, size_t path_size)
193 {
194  LIBSSHContext *libssh = h->priv_data;
195  char proto[10], hostname[1024], credencials[1024];
196  int port = 22, ret;
197  const char *user = NULL, *pass = NULL;
198  char *end = NULL;
199 
200  av_url_split(proto, sizeof(proto),
201  credencials, sizeof(credencials),
202  hostname, sizeof(hostname),
203  &port,
204  path, path_size,
205  url);
206 
207  if (!(*path))
208  av_strlcpy(path, "/", path_size);
209 
210  // a port of 0 will use a port from ~/.ssh/config or the default value 22
211  if (port < 0 || port > 65535)
212  port = 0;
213 
214  if ((ret = libssh_create_ssh_session(libssh, hostname, port)) < 0)
215  return ret;
216 
217  user = av_strtok(credencials, ":", &end);
218  pass = av_strtok(end, ":", &end);
219 
220  if ((ret = libssh_authentication(libssh, user, pass)) < 0)
221  return ret;
222 
223  if ((ret = libssh_create_sftp_session(libssh)) < 0)
224  return ret;
225 
226  return 0;
227 }
228 
229 static av_cold int libssh_open(URLContext *h, const char *url, int flags)
230 {
231  int ret;
232  LIBSSHContext *libssh = h->priv_data;
233  char path[MAX_URL_SIZE];
234 
235  if ((ret = libssh_connect(h, url, path, sizeof(path))) < 0)
236  goto fail;
237 
238  if ((ret = libssh_open_file(libssh, flags, path)) < 0)
239  goto fail;
240 
241  libssh_stat_file(libssh);
242 
243  return 0;
244 
245  fail:
246  libssh_close(h);
247  return ret;
248 }
249 
250 static int64_t libssh_seek(URLContext *h, int64_t pos, int whence)
251 {
252  LIBSSHContext *libssh = h->priv_data;
253  int64_t newpos;
254 
255  if (libssh->filesize == -1 && (whence == AVSEEK_SIZE || whence == SEEK_END)) {
256  av_log(h, AV_LOG_ERROR, "Error during seeking.\n");
257  return AVERROR(EIO);
258  }
259 
260  switch(whence) {
261  case AVSEEK_SIZE:
262  return libssh->filesize;
263  case SEEK_SET:
264  newpos = pos;
265  break;
266  case SEEK_CUR:
267  newpos = sftp_tell64(libssh->file) + pos;
268  break;
269  case SEEK_END:
270  newpos = libssh->filesize + pos;
271  break;
272  default:
273  return AVERROR(EINVAL);
274  }
275 
276  if (newpos < 0) {
277  av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n");
278  return AVERROR(EINVAL);
279  }
280 
281  if (sftp_seek64(libssh->file, newpos)) {
282  av_log(h, AV_LOG_ERROR, "Error during seeking.\n");
283  return AVERROR(EIO);
284  }
285 
286  return newpos;
287 }
288 
289 static int libssh_read(URLContext *h, unsigned char *buf, int size)
290 {
291  LIBSSHContext *libssh = h->priv_data;
292  int bytes_read;
293 
294  if ((bytes_read = sftp_read(libssh->file, buf, size)) < 0) {
295  av_log(libssh, AV_LOG_ERROR, "Read error.\n");
296  return AVERROR(EIO);
297  }
298  return bytes_read;
299 }
300 
301 static int libssh_write(URLContext *h, const unsigned char *buf, int size)
302 {
303  LIBSSHContext *libssh = h->priv_data;
304  int bytes_written;
305 
306  if ((bytes_written = sftp_write(libssh->file, buf, size)) < 0) {
307  av_log(libssh, AV_LOG_ERROR, "Write error.\n");
308  return AVERROR(EIO);
309  }
310  return bytes_written;
311 }
312 
314 {
315  LIBSSHContext *libssh = h->priv_data;
316  int ret;
317  char path[MAX_URL_SIZE];
318 
319  if ((ret = libssh_connect(h, h->filename, path, sizeof(path))) < 0)
320  goto fail;
321 
322  if (!(libssh->dir = sftp_opendir(libssh->sftp, path))) {
323  av_log(libssh, AV_LOG_ERROR, "Error opening sftp dir: %s\n", ssh_get_error(libssh->session));
324  ret = AVERROR(EIO);
325  goto fail;
326  }
327 
328  return 0;
329 
330  fail:
331  libssh_close(h);
332  return ret;
333 }
334 
336 {
337  LIBSSHContext *libssh = h->priv_data;
338  sftp_attributes attr = NULL;
339  AVIODirEntry *entry;
340 
341  *next = entry = ff_alloc_dir_entry();
342  if (!entry)
343  return AVERROR(ENOMEM);
344 
345  do {
346  if (attr)
347  sftp_attributes_free(attr);
348  attr = sftp_readdir(libssh->sftp, libssh->dir);
349  if (!attr) {
350  av_freep(next);
351  if (sftp_dir_eof(libssh->dir))
352  return 0;
353  return AVERROR(EIO);
354  }
355  } while (!strcmp(attr->name, ".") || !strcmp(attr->name, ".."));
356 
357  entry->name = av_strdup(attr->name);
358  entry->group_id = attr->gid;
359  entry->user_id = attr->uid;
360  entry->size = attr->size;
361  entry->access_timestamp = INT64_C(1000000) * attr->atime;
362  entry->modification_timestamp = INT64_C(1000000) * attr->mtime;
363  entry->filemode = attr->permissions & 0777;
364  switch(attr->type) {
365  case SSH_FILEXFER_TYPE_REGULAR:
366  entry->type = AVIO_ENTRY_FILE;
367  break;
368  case SSH_FILEXFER_TYPE_DIRECTORY:
369  entry->type = AVIO_ENTRY_DIRECTORY;
370  break;
371  case SSH_FILEXFER_TYPE_SYMLINK:
373  break;
374  case SSH_FILEXFER_TYPE_SPECIAL:
375  /* Special type includes: sockets, char devices, block devices and pipes.
376  It is probably better to return unknown type, to not confuse anybody. */
377  case SSH_FILEXFER_TYPE_UNKNOWN:
378  default:
379  entry->type = AVIO_ENTRY_UNKNOWN;
380  }
381  sftp_attributes_free(attr);
382  return 0;
383 }
384 
386 {
387  LIBSSHContext *libssh = h->priv_data;
388  if (libssh->dir)
389  sftp_closedir(libssh->dir);
390  libssh->dir = NULL;
391  libssh_close(h);
392  return 0;
393 }
394 
396 {
397  int ret;
398  LIBSSHContext *libssh = h->priv_data;
399  sftp_attributes attr = NULL;
400  char path[MAX_URL_SIZE];
401 
402  if ((ret = libssh_connect(h, h->filename, path, sizeof(path))) < 0)
403  goto cleanup;
404 
405  if (!(attr = sftp_stat(libssh->sftp, path))) {
406  ret = AVERROR(sftp_get_error(libssh->sftp));
407  goto cleanup;
408  }
409 
410  if (attr->type == SSH_FILEXFER_TYPE_DIRECTORY) {
411  if (sftp_rmdir(libssh->sftp, path) < 0) {
412  ret = AVERROR(sftp_get_error(libssh->sftp));
413  goto cleanup;
414  }
415  } else {
416  if (sftp_unlink(libssh->sftp, path) < 0) {
417  ret = AVERROR(sftp_get_error(libssh->sftp));
418  goto cleanup;
419  }
420  }
421 
422  ret = 0;
423 
424 cleanup:
425  if (attr)
426  sftp_attributes_free(attr);
427  libssh_close(h);
428  return ret;
429 }
430 
431 static int libssh_move(URLContext *h_src, URLContext *h_dst)
432 {
433  int ret;
434  LIBSSHContext *libssh = h_src->priv_data;
435  char path_src[MAX_URL_SIZE], path_dst[MAX_URL_SIZE];
436  char hostname_src[1024], hostname_dst[1024];
437  char credentials_src[1024], credentials_dst[1024];
438  int port_src = 22, port_dst = 22;
439 
440  av_url_split(NULL, 0,
441  credentials_src, sizeof(credentials_src),
442  hostname_src, sizeof(hostname_src),
443  &port_src,
444  path_src, sizeof(path_src),
445  h_src->filename);
446 
447  av_url_split(NULL, 0,
448  credentials_dst, sizeof(credentials_dst),
449  hostname_dst, sizeof(hostname_dst),
450  &port_dst,
451  path_dst, sizeof(path_dst),
452  h_dst->filename);
453 
454  if (strcmp(credentials_src, credentials_dst) ||
455  strcmp(hostname_src, hostname_dst) ||
456  port_src != port_dst) {
457  return AVERROR(EINVAL);
458  }
459 
460  if ((ret = libssh_connect(h_src, h_src->filename, path_src, sizeof(path_src))) < 0)
461  goto cleanup;
462 
463  if (sftp_rename(libssh->sftp, path_src, path_dst) < 0) {
464  ret = AVERROR(sftp_get_error(libssh->sftp));
465  goto cleanup;
466  }
467 
468  ret = 0;
469 
470 cleanup:
471  libssh_close(h_src);
472  return ret;
473 }
474 
475 #define OFFSET(x) offsetof(LIBSSHContext, x)
476 #define D AV_OPT_FLAG_DECODING_PARAM
477 #define E AV_OPT_FLAG_ENCODING_PARAM
478 static const AVOption options[] = {
479  {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, -1, INT_MAX, D|E },
480  {"truncate", "Truncate existing files on write", OFFSET(trunc), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, E },
481  {"private_key", "set path to private key", OFFSET(priv_key), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D|E },
482  {NULL}
483 };
484 
486  .class_name = "libssh",
487  .item_name = av_default_item_name,
488  .option = options,
489  .version = LIBAVUTIL_VERSION_INT,
490 };
491 
493  .name = "sftp",
494  .url_open = libssh_open,
495  .url_read = libssh_read,
496  .url_write = libssh_write,
497  .url_seek = libssh_seek,
498  .url_close = libssh_close,
499  .url_delete = libssh_delete,
500  .url_move = libssh_move,
501  .url_open_dir = libssh_open_dir,
502  .url_read_dir = libssh_read_dir,
503  .url_close_dir = libssh_close_dir,
504  .priv_data_size = sizeof(LIBSSHContext),
505  .priv_data_class = &libssh_context_class,
507 };
void av_url_split(char *proto, int proto_size, char *authorization, int authorization_size, char *hostname, int hostname_size, int *port_ptr, char *path, int path_size, const char *url)
Split a URL string into components.
Definition: utils.c:4691
static av_cold int libssh_open(URLContext *h, const char *url, int flags)
Definition: libssh.c:229
#define NULL
Definition: coverity.c:32
sftp_dir dir
Definition: libssh.c:37
#define OFFSET(x)
Definition: libssh.c:475
Buffered I/O operations.
int size
#define URL_PROTOCOL_FLAG_NETWORK
Definition: url.h:34
static int libssh_write(URLContext *h, const unsigned char *buf, int size)
Definition: libssh.c:301
AVOption.
Definition: opt.h:246
int64_t filemode
Unix file mode, -1 if unknown.
Definition: avio.h:100
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition: log.h:182
int rw_timeout
Definition: libssh.c:39
#define LIBAVUTIL_VERSION_INT
Definition: version.h:85
Describes single entry of the directory.
Definition: avio.h:86
const char * av_default_item_name(void *ptr)
Return the context name.
Definition: log.c:191
#define AVIO_FLAG_READ
read-only
Definition: avio.h:654
#define AVIO_FLAG_WRITE
write-only
Definition: avio.h:655
Macro definitions for various function/variable attributes.
static int libssh_open_dir(URLContext *h)
Definition: libssh.c:313
#define MAX_URL_SIZE
Definition: internal.h:30
int64_t modification_timestamp
Time of last modification in microseconds since unix epoch, -1 if unknown.
Definition: avio.h:92
const char * class_name
The name of the class; usually it is the same name as the context structure type to which the AVClass...
Definition: log.h:72
#define av_cold
Definition: attributes.h:82
AVOptions.
static av_cold int end(AVCodecContext *avctx)
Definition: avrndec.c:90
#define D
Definition: libssh.c:476
static int libssh_read_dir(URLContext *h, AVIODirEntry **next)
Definition: libssh.c:335
static int flags
Definition: log.c:55
static int libssh_move(URLContext *h_src, URLContext *h_dst)
Definition: libssh.c:431
static int libssh_delete(URLContext *h)
Definition: libssh.c:395
#define av_log(a,...)
char * name
Filename.
Definition: avio.h:87
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:176
#define AVERROR(e)
Definition: error.h:43
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition: log.h:197
static av_cold int libssh_create_ssh_session(LIBSSHContext *libssh, const char *hostname, unsigned int port)
Definition: libssh.c:44
static av_cold int libssh_create_sftp_session(LIBSSHContext *libssh)
Definition: libssh.c:121
static av_cold int libssh_authentication(LIBSSHContext *libssh, const char *user, const char *password)
Definition: libssh.c:72
sftp_session sftp
Definition: libssh.c:35
size_t av_strlcpy(char *dst, const char *src, size_t size)
Copy the string src to dst, but no more than size - 1 bytes, and null-terminate dst.
Definition: avstring.c:83
#define fail()
Definition: checkasm.h:116
#define pass
Definition: fft_template.c:593
int64_t access_timestamp
Time of last access in microseconds since unix epoch, -1 if unknown.
Definition: avio.h:94
static av_always_inline av_const double trunc(double x)
Definition: libm.h:458
static av_cold int libssh_open_file(LIBSSHContext *libssh, int flags, const char *file)
Definition: libssh.c:136
static av_cold int libssh_close(URLContext *h)
Definition: libssh.c:173
static const AVClass libssh_context_class
Definition: libssh.c:485
ssh_session session
Definition: libssh.c:34
int64_t size
File size in bytes, -1 if unknown.
Definition: avio.h:91
const URLProtocol ff_libssh_protocol
Definition: libssh.c:492
int64_t filesize
Definition: libssh.c:38
static int64_t libssh_seek(URLContext *h, int64_t pos, int whence)
Definition: libssh.c:250
char * av_strdup(const char *s)
Duplicate a string.
Definition: mem.c:251
int type
Type of the entry.
Definition: avio.h:88
#define E
Definition: libssh.c:477
char * priv_key
Definition: libssh.c:41
void * buf
Definition: avisynth_c.h:690
Definition: url.h:38
Describe the class of an AVClass context structure.
Definition: log.h:67
int64_t group_id
Group ID of owner, -1 if unknown.
Definition: avio.h:99
static const AVOption options[]
Definition: libssh.c:478
void * priv_data
Definition: url.h:41
cl_device_type type
static int libssh_read(URLContext *h, unsigned char *buf, int size)
Definition: libssh.c:289
static av_cold int libssh_connect(URLContext *h, const char *url, char *path, size_t path_size)
Definition: libssh.c:192
const char * name
Definition: url.h:55
char * av_strtok(char *s, const char *delim, char **saveptr)
Split the string into several tokens which can be accessed by successive calls to av_strtok()...
Definition: avstring.c:184
Main libavformat public API header.
sftp_file file
Definition: libssh.c:36
AVIODirEntry * ff_alloc_dir_entry(void)
Allocate directory entry with default values.
Definition: url.c:149
char * filename
specified URL
Definition: url.h:42
#define AVSEEK_SIZE
ORing this as the "whence" parameter to a seek function causes it to return the filesize without seek...
Definition: avio.h:531
static int libssh_close_dir(URLContext *h)
Definition: libssh.c:385
int64_t user_id
User ID of owner, -1 if unknown.
Definition: avio.h:98
int trunc
Definition: libssh.c:40
static av_cold void libssh_stat_file(LIBSSHContext *libssh)
Definition: libssh.c:160
#define av_freep(p)
unbuffered private I/O API
static av_cold void cleanup(FlashSV2Context *s)
Definition: flashsv2enc.c:127