FFmpeg
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 ? bytes_read : AVERROR_EOF;
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:4738
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.
#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:674
#define AVIO_FLAG_WRITE
write-only
Definition: avio.h:675
GLint GLenum type
Definition: opengl_enc.c:104
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
#define AVERROR_EOF
End of file.
Definition: error.h:55
ptrdiff_t size
Definition: opengl_enc.c:100
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 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:120
#define pass
Definition: fft_template.c:619
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:766
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
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
#define flags(name, subs,...)
Definition: cbs_av1.c:561
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
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later.That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another.Frame references ownership and permissions
static av_cold void cleanup(FlashSV2Context *s)
Definition: flashsv2enc.c:127