• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
39     int rw_timeout;
40     int trunc;
41     char *priv_key;
42 } LIBSSHContext;
43 
libssh_create_ssh_session(LIBSSHContext * libssh,const char * hostname,unsigned int port)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 
libssh_authentication(LIBSSHContext * libssh,const char * user,const char * password)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 
libssh_create_sftp_session(LIBSSHContext * libssh)121 static av_cold int libssh_create_sftp_session(LIBSSHContext *libssh)
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 
libssh_open_file(LIBSSHContext * libssh,int flags,const char * file)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 
libssh_stat_file(LIBSSHContext * libssh)160 static av_cold void libssh_stat_file(LIBSSHContext *libssh)
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 
libssh_close(URLContext * h)173 static av_cold int libssh_close(URLContext *h)
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 
libssh_connect(URLContext * h,const char * url,char * path,size_t path_size)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 
libssh_open(URLContext * h,const char * url,int flags)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 
libssh_seek(URLContext * h,int64_t pos,int whence)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 
libssh_read(URLContext * h,unsigned char * buf,int size)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 
libssh_write(URLContext * h,const unsigned char * buf,int size)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 
libssh_open_dir(URLContext * h)313 static int libssh_open_dir(URLContext *h)
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 
libssh_read_dir(URLContext * h,AVIODirEntry ** next)335 static int libssh_read_dir(URLContext *h, AVIODirEntry **next)
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:
372         entry->type = AVIO_ENTRY_SYMBOLIC_LINK;
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 
libssh_close_dir(URLContext * h)385 static int libssh_close_dir(URLContext *h)
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 
libssh_delete(URLContext * h)395 static int libssh_delete(URLContext *h)
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 
libssh_move(URLContext * h_src,URLContext * h_dst)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 
485 static const AVClass libssh_context_class = {
486     .class_name     = "libssh",
487     .item_name      = av_default_item_name,
488     .option         = options,
489     .version        = LIBAVUTIL_VERSION_INT,
490 };
491 
492 const URLProtocol ff_libssh_protocol = {
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,
506     .flags               = URL_PROTOCOL_FLAG_NETWORK,
507 };
508