2 * Copyright (c) 2013 Lukasz Marek <lukasz.m.luki@gmail.com>
4 * This file is part of FFmpeg.
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.
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.
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
23 #include <libssh/sftp.h>
24 #include "libavutil/avstring.h"
25 #include "libavutil/mem.h"
26 #include "libavutil/opt.h"
27 #include "libavutil/attributes.h"
28 #include "libavformat/avio.h"
45 static av_cold
int libssh_create_ssh_session(LIBSSHContext
*libssh
, const char* hostname
, unsigned int port
)
47 static const int verbosity
= SSH_LOG_NOLOG
;
49 if (!(libssh
->session
= ssh_new())) {
50 av_log(libssh
, AV_LOG_ERROR
, "SSH session creation failed: %s\n", ssh_get_error(libssh
->session
));
51 return AVERROR(ENOMEM
);
53 ssh_options_set(libssh
->session
, SSH_OPTIONS_HOST
, hostname
);
54 ssh_options_set(libssh
->session
, SSH_OPTIONS_PORT
, &port
);
55 ssh_options_set(libssh
->session
, SSH_OPTIONS_LOG_VERBOSITY
, &verbosity
);
56 if (libssh
->rw_timeout
> 0) {
57 long timeout
= libssh
->rw_timeout
* 1000;
58 ssh_options_set(libssh
->session
, SSH_OPTIONS_TIMEOUT_USEC
, &timeout
);
61 if (ssh_options_parse_config(libssh
->session
, NULL
) < 0) {
62 av_log(libssh
, AV_LOG_WARNING
, "Could not parse the config file.\n");
65 if (ssh_connect(libssh
->session
) != SSH_OK
) {
66 av_log(libssh
, AV_LOG_ERROR
, "Connection failed: %s\n", ssh_get_error(libssh
->session
));
73 static av_cold
int libssh_authentication(LIBSSHContext
*libssh
, const char *user
, const char *password
)
79 ssh_options_set(libssh
->session
, SSH_OPTIONS_USER
, user
);
81 if (ssh_userauth_none(libssh
->session
, NULL
) == SSH_AUTH_SUCCESS
)
84 auth_methods
= ssh_userauth_list(libssh
->session
, NULL
);
86 if (auth_methods
& SSH_AUTH_METHOD_PUBLICKEY
) {
87 if (libssh
->priv_key
) {
89 if (ssh_pki_import_privkey_file(libssh
->priv_key
, password
, NULL
, NULL
, &priv_key
) == SSH_OK
) {
90 if (ssh_userauth_publickey(libssh
->session
, NULL
, priv_key
) == SSH_AUTH_SUCCESS
) {
91 av_log(libssh
, AV_LOG_DEBUG
, "Authentication successful with selected private key.\n");
95 av_log(libssh
, AV_LOG_DEBUG
, "Invalid key is provided.\n");
96 return AVERROR(EACCES
);
98 } else if (ssh_userauth_publickey_auto(libssh
->session
, NULL
, password
) == SSH_AUTH_SUCCESS
) {
99 av_log(libssh
, AV_LOG_DEBUG
, "Authentication successful with auto selected key.\n");
104 if (!authorized
&& password
&& (auth_methods
& SSH_AUTH_METHOD_PASSWORD
)) {
105 if (ssh_userauth_password(libssh
->session
, NULL
, password
) == SSH_AUTH_SUCCESS
) {
106 av_log(libssh
, AV_LOG_DEBUG
, "Authentication successful with password.\n");
112 av_log(libssh
, AV_LOG_ERROR
, "Authentication failed.\n");
113 return AVERROR(EACCES
);
119 static av_cold
int libssh_create_sftp_session(LIBSSHContext
*libssh
)
121 if (!(libssh
->sftp
= sftp_new(libssh
->session
))) {
122 av_log(libssh
, AV_LOG_ERROR
, "SFTP session creation failed: %s\n", ssh_get_error(libssh
->session
));
123 return AVERROR(ENOMEM
);
126 if (sftp_init(libssh
->sftp
) != SSH_OK
) {
127 av_log(libssh
, AV_LOG_ERROR
, "Error initializing sftp session: %s\n", ssh_get_error(libssh
->session
));
134 static av_cold
int libssh_open_file(LIBSSHContext
*libssh
, int flags
, const char *file
)
138 if ((flags
& AVIO_FLAG_WRITE
) && (flags
& AVIO_FLAG_READ
)) {
139 access
= O_CREAT
| O_RDWR
;
142 } else if (flags
& AVIO_FLAG_WRITE
) {
143 access
= O_CREAT
| O_WRONLY
;
149 /* 0666 = -rw-rw-rw- = read+write for everyone, minus umask */
150 if (!(libssh
->file
= sftp_open(libssh
->sftp
, file
, access
, 0666))) {
151 av_log(libssh
, AV_LOG_ERROR
, "Error opening sftp file: %s\n", ssh_get_error(libssh
->session
));
158 static av_cold
void libssh_stat_file(LIBSSHContext
*libssh
)
160 sftp_attributes stat
;
162 if (!(stat
= sftp_fstat(libssh
->file
))) {
163 av_log(libssh
, AV_LOG_WARNING
, "Cannot stat remote file.\n");
164 libssh
->filesize
= -1;
166 libssh
->filesize
= stat
->size
;
167 sftp_attributes_free(stat
);
171 static av_cold
int libssh_close(URLContext
*h
)
173 LIBSSHContext
*libssh
= h
->priv_data
;
175 sftp_close(libssh
->file
);
179 sftp_free(libssh
->sftp
);
182 if (libssh
->session
) {
183 ssh_disconnect(libssh
->session
);
184 ssh_free(libssh
->session
);
185 libssh
->session
= NULL
;
190 static av_cold
int libssh_connect(URLContext
*h
, const char *url
, char *path
, size_t path_size
)
192 LIBSSHContext
*libssh
= h
->priv_data
;
193 char proto
[10], hostname
[1024], credentials
[1024];
195 const char *user
= NULL
, *pass
= NULL
;
198 av_url_split(proto
, sizeof(proto
),
199 credentials
, sizeof(credentials
),
200 hostname
, sizeof(hostname
),
206 av_strlcpy(path
, "/", path_size
);
208 // a port of 0 will use a port from ~/.ssh/config or the default value 22
209 if (port
< 0 || port
> 65535)
212 if ((ret
= libssh_create_ssh_session(libssh
, hostname
, port
)) < 0)
215 user
= av_strtok(credentials
, ":", &end
);
216 pass
= av_strtok(end
, ":", &end
);
218 if ((ret
= libssh_authentication(libssh
, user
, pass
)) < 0)
221 if ((ret
= libssh_create_sftp_session(libssh
)) < 0)
227 static av_cold
int libssh_open(URLContext
*h
, const char *url
, int flags
)
230 LIBSSHContext
*libssh
= h
->priv_data
;
231 char path
[MAX_URL_SIZE
];
233 if ((ret
= libssh_connect(h
, url
, path
, sizeof(path
))) < 0)
236 if ((ret
= libssh_open_file(libssh
, flags
, path
)) < 0)
239 libssh_stat_file(libssh
);
248 static int64_t libssh_seek(URLContext
*h
, int64_t pos
, int whence
)
250 LIBSSHContext
*libssh
= h
->priv_data
;
253 if (libssh
->filesize
== -1 && (whence
== AVSEEK_SIZE
|| whence
== SEEK_END
)) {
254 av_log(h
, AV_LOG_ERROR
, "Error during seeking.\n");
260 return libssh
->filesize
;
265 newpos
= sftp_tell64(libssh
->file
) + pos
;
268 newpos
= libssh
->filesize
+ pos
;
271 return AVERROR(EINVAL
);
275 av_log(h
, AV_LOG_ERROR
, "Seeking to nagative position.\n");
276 return AVERROR(EINVAL
);
279 if (sftp_seek64(libssh
->file
, newpos
)) {
280 av_log(h
, AV_LOG_ERROR
, "Error during seeking.\n");
287 static int libssh_read(URLContext
*h
, unsigned char *buf
, int size
)
289 LIBSSHContext
*libssh
= h
->priv_data
;
292 if ((bytes_read
= sftp_read(libssh
->file
, buf
, size
)) < 0) {
293 av_log(libssh
, AV_LOG_ERROR
, "Read error.\n");
296 return bytes_read
? bytes_read
: AVERROR_EOF
;
299 static int libssh_write(URLContext
*h
, const unsigned char *buf
, int size
)
301 LIBSSHContext
*libssh
= h
->priv_data
;
304 if ((bytes_written
= sftp_write(libssh
->file
, buf
, size
)) < 0) {
305 av_log(libssh
, AV_LOG_ERROR
, "Write error.\n");
308 return bytes_written
;
311 static int libssh_open_dir(URLContext
*h
)
313 LIBSSHContext
*libssh
= h
->priv_data
;
315 char path
[MAX_URL_SIZE
];
317 if ((ret
= libssh_connect(h
, h
->filename
, path
, sizeof(path
))) < 0)
320 if (!(libssh
->dir
= sftp_opendir(libssh
->sftp
, path
))) {
321 av_log(libssh
, AV_LOG_ERROR
, "Error opening sftp dir: %s\n", ssh_get_error(libssh
->session
));
333 static int libssh_read_dir(URLContext
*h
, AVIODirEntry
**next
)
335 LIBSSHContext
*libssh
= h
->priv_data
;
336 sftp_attributes attr
= NULL
;
339 *next
= entry
= ff_alloc_dir_entry();
341 return AVERROR(ENOMEM
);
345 sftp_attributes_free(attr
);
346 attr
= sftp_readdir(libssh
->sftp
, libssh
->dir
);
349 if (sftp_dir_eof(libssh
->dir
))
353 } while (!strcmp(attr
->name
, ".") || !strcmp(attr
->name
, ".."));
355 entry
->name
= av_strdup(attr
->name
);
356 entry
->group_id
= attr
->gid
;
357 entry
->user_id
= attr
->uid
;
358 entry
->size
= attr
->size
;
359 entry
->access_timestamp
= INT64_C(1000000) * attr
->atime
;
360 entry
->modification_timestamp
= INT64_C(1000000) * attr
->mtime
;
361 entry
->filemode
= attr
->permissions
& 0777;
363 case SSH_FILEXFER_TYPE_REGULAR
:
364 entry
->type
= AVIO_ENTRY_FILE
;
366 case SSH_FILEXFER_TYPE_DIRECTORY
:
367 entry
->type
= AVIO_ENTRY_DIRECTORY
;
369 case SSH_FILEXFER_TYPE_SYMLINK
:
370 entry
->type
= AVIO_ENTRY_SYMBOLIC_LINK
;
372 case SSH_FILEXFER_TYPE_SPECIAL
:
373 /* Special type includes: sockets, char devices, block devices and pipes.
374 It is probably better to return unknown type, to not confuse anybody. */
375 case SSH_FILEXFER_TYPE_UNKNOWN
:
377 entry
->type
= AVIO_ENTRY_UNKNOWN
;
379 sftp_attributes_free(attr
);
383 static int libssh_close_dir(URLContext
*h
)
385 LIBSSHContext
*libssh
= h
->priv_data
;
387 sftp_closedir(libssh
->dir
);
393 static int libssh_delete(URLContext
*h
)
396 LIBSSHContext
*libssh
= h
->priv_data
;
397 sftp_attributes attr
= NULL
;
398 char path
[MAX_URL_SIZE
];
400 if ((ret
= libssh_connect(h
, h
->filename
, path
, sizeof(path
))) < 0)
403 if (!(attr
= sftp_stat(libssh
->sftp
, path
))) {
404 ret
= AVERROR(sftp_get_error(libssh
->sftp
));
408 if (attr
->type
== SSH_FILEXFER_TYPE_DIRECTORY
) {
409 if (sftp_rmdir(libssh
->sftp
, path
) < 0) {
410 ret
= AVERROR(sftp_get_error(libssh
->sftp
));
414 if (sftp_unlink(libssh
->sftp
, path
) < 0) {
415 ret
= AVERROR(sftp_get_error(libssh
->sftp
));
424 sftp_attributes_free(attr
);
429 static int libssh_move(URLContext
*h_src
, URLContext
*h_dst
)
432 LIBSSHContext
*libssh
= h_src
->priv_data
;
433 char path_src
[MAX_URL_SIZE
], path_dst
[MAX_URL_SIZE
];
434 char hostname_src
[1024], hostname_dst
[1024];
435 char credentials_src
[1024], credentials_dst
[1024];
436 int port_src
= 22, port_dst
= 22;
438 av_url_split(NULL
, 0,
439 credentials_src
, sizeof(credentials_src
),
440 hostname_src
, sizeof(hostname_src
),
442 path_src
, sizeof(path_src
),
445 av_url_split(NULL
, 0,
446 credentials_dst
, sizeof(credentials_dst
),
447 hostname_dst
, sizeof(hostname_dst
),
449 path_dst
, sizeof(path_dst
),
452 if (strcmp(credentials_src
, credentials_dst
) ||
453 strcmp(hostname_src
, hostname_dst
) ||
454 port_src
!= port_dst
) {
455 return AVERROR(EINVAL
);
458 if ((ret
= libssh_connect(h_src
, h_src
->filename
, path_src
, sizeof(path_src
))) < 0)
461 if (sftp_rename(libssh
->sftp
, path_src
, path_dst
) < 0) {
462 ret
= AVERROR(sftp_get_error(libssh
->sftp
));
473 #define OFFSET(x) offsetof(LIBSSHContext, x)
474 #define D AV_OPT_FLAG_DECODING_PARAM
475 #define E AV_OPT_FLAG_ENCODING_PARAM
476 static const AVOption options
[] = {
477 {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout
), AV_OPT_TYPE_INT
, {.i64
= -1}, -1, INT_MAX
, D
|E
},
478 {"truncate", "Truncate existing files on write", OFFSET(trunc
), AV_OPT_TYPE_INT
, { .i64
= 1 }, 0, 1, E
},
479 {"private_key", "set path to private key", OFFSET(priv_key
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, D
|E
},
483 static const AVClass libssh_context_class
= {
484 .class_name
= "libssh",
485 .item_name
= av_default_item_name
,
487 .version
= LIBAVUTIL_VERSION_INT
,
490 const URLProtocol ff_libssh_protocol
= {
492 .url_open
= libssh_open
,
493 .url_read
= libssh_read
,
494 .url_write
= libssh_write
,
495 .url_seek
= libssh_seek
,
496 .url_close
= libssh_close
,
497 .url_delete
= libssh_delete
,
498 .url_move
= libssh_move
,
499 .url_open_dir
= libssh_open_dir
,
500 .url_read_dir
= libssh_read_dir
,
501 .url_close_dir
= libssh_close_dir
,
502 .priv_data_size
= sizeof(LIBSSHContext
),
503 .priv_data_class
= &libssh_context_class
,
504 .flags
= URL_PROTOCOL_FLAG_NETWORK
,