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/opt.h"
26 #include "libavutil/attributes.h"
27 #include "libavformat/avio.h"
44 static av_cold
int libssh_create_ssh_session(LIBSSHContext
*libssh
, const char* hostname
, unsigned int port
)
46 static const int verbosity
= SSH_LOG_NOLOG
;
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
);
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
);
60 if (ssh_options_parse_config(libssh
->session
, NULL
) < 0) {
61 av_log(libssh
, AV_LOG_WARNING
, "Could not parse the config file.\n");
64 if (ssh_connect(libssh
->session
) != SSH_OK
) {
65 av_log(libssh
, AV_LOG_ERROR
, "Connection failed: %s\n", ssh_get_error(libssh
->session
));
72 static av_cold
int libssh_authentication(LIBSSHContext
*libssh
, const char *user
, const char *password
)
78 ssh_options_set(libssh
->session
, SSH_OPTIONS_USER
, user
);
80 if (ssh_userauth_none(libssh
->session
, NULL
) == SSH_AUTH_SUCCESS
)
83 auth_methods
= ssh_userauth_list(libssh
->session
, NULL
);
85 if (auth_methods
& SSH_AUTH_METHOD_PUBLICKEY
) {
86 if (libssh
->priv_key
) {
88 ssh_private_key priv_key
;
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");
97 av_log(libssh
, AV_LOG_DEBUG
, "Invalid key is provided.\n");
98 return AVERROR(EACCES
);
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");
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");
114 av_log(libssh
, AV_LOG_ERROR
, "Authentication failed.\n");
115 return AVERROR(EACCES
);
121 static av_cold
int libssh_create_sftp_session(LIBSSHContext
*libssh
)
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
);
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
));
136 static av_cold
int libssh_open_file(LIBSSHContext
*libssh
, int flags
, const char *file
)
140 if ((flags
& AVIO_FLAG_WRITE
) && (flags
& AVIO_FLAG_READ
)) {
141 access
= O_CREAT
| O_RDWR
;
144 } else if (flags
& AVIO_FLAG_WRITE
) {
145 access
= O_CREAT
| O_WRONLY
;
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
));
160 static av_cold
void libssh_stat_file(LIBSSHContext
*libssh
)
162 sftp_attributes stat
;
164 if (!(stat
= sftp_fstat(libssh
->file
))) {
165 av_log(libssh
, AV_LOG_WARNING
, "Cannot stat remote file.\n");
166 libssh
->filesize
= -1;
168 libssh
->filesize
= stat
->size
;
169 sftp_attributes_free(stat
);
173 static av_cold
int libssh_close(URLContext
*h
)
175 LIBSSHContext
*libssh
= h
->priv_data
;
177 sftp_close(libssh
->file
);
181 sftp_free(libssh
->sftp
);
184 if (libssh
->session
) {
185 ssh_disconnect(libssh
->session
);
186 ssh_free(libssh
->session
);
187 libssh
->session
= NULL
;
192 static av_cold
int libssh_connect(URLContext
*h
, const char *url
, char *path
, size_t path_size
)
194 LIBSSHContext
*libssh
= h
->priv_data
;
195 char proto
[10], hostname
[1024], credencials
[1024];
197 const char *user
= NULL
, *pass
= NULL
;
200 av_url_split(proto
, sizeof(proto
),
201 credencials
, sizeof(credencials
),
202 hostname
, sizeof(hostname
),
208 av_strlcpy(path
, "/", path_size
);
210 // a port of 0 will use a port from ~/.ssh/config or the default value 22
211 if (port
< 0 || port
> 65535)
214 if ((ret
= libssh_create_ssh_session(libssh
, hostname
, port
)) < 0)
217 user
= av_strtok(credencials
, ":", &end
);
218 pass
= av_strtok(end
, ":", &end
);
220 if ((ret
= libssh_authentication(libssh
, user
, pass
)) < 0)
223 if ((ret
= libssh_create_sftp_session(libssh
)) < 0)
229 static av_cold
int libssh_open(URLContext
*h
, const char *url
, int flags
)
232 LIBSSHContext
*libssh
= h
->priv_data
;
233 char path
[MAX_URL_SIZE
];
235 if ((ret
= libssh_connect(h
, url
, path
, sizeof(path
))) < 0)
238 if ((ret
= libssh_open_file(libssh
, flags
, path
)) < 0)
241 libssh_stat_file(libssh
);
250 static int64_t libssh_seek(URLContext
*h
, int64_t pos
, int whence
)
252 LIBSSHContext
*libssh
= h
->priv_data
;
255 if (libssh
->filesize
== -1 && (whence
== AVSEEK_SIZE
|| whence
== SEEK_END
)) {
256 av_log(h
, AV_LOG_ERROR
, "Error during seeking.\n");
262 return libssh
->filesize
;
267 newpos
= sftp_tell64(libssh
->file
) + pos
;
270 newpos
= libssh
->filesize
+ pos
;
273 return AVERROR(EINVAL
);
277 av_log(h
, AV_LOG_ERROR
, "Seeking to nagative position.\n");
278 return AVERROR(EINVAL
);
281 if (sftp_seek64(libssh
->file
, newpos
)) {
282 av_log(h
, AV_LOG_ERROR
, "Error during seeking.\n");
289 static int libssh_read(URLContext
*h
, unsigned char *buf
, int size
)
291 LIBSSHContext
*libssh
= h
->priv_data
;
294 if ((bytes_read
= sftp_read(libssh
->file
, buf
, size
)) < 0) {
295 av_log(libssh
, AV_LOG_ERROR
, "Read error.\n");
301 static int libssh_write(URLContext
*h
, const unsigned char *buf
, int size
)
303 LIBSSHContext
*libssh
= h
->priv_data
;
306 if ((bytes_written
= sftp_write(libssh
->file
, buf
, size
)) < 0) {
307 av_log(libssh
, AV_LOG_ERROR
, "Write error.\n");
310 return bytes_written
;
313 static int libssh_open_dir(URLContext
*h
)
315 LIBSSHContext
*libssh
= h
->priv_data
;
317 char path
[MAX_URL_SIZE
];
319 if ((ret
= libssh_connect(h
, h
->filename
, path
, sizeof(path
))) < 0)
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
));
335 static int libssh_read_dir(URLContext
*h
, AVIODirEntry
**next
)
337 LIBSSHContext
*libssh
= h
->priv_data
;
338 sftp_attributes attr
= NULL
;
341 *next
= entry
= ff_alloc_dir_entry();
343 return AVERROR(ENOMEM
);
347 sftp_attributes_free(attr
);
348 attr
= sftp_readdir(libssh
->sftp
, libssh
->dir
);
351 if (sftp_dir_eof(libssh
->dir
))
355 } while (!strcmp(attr
->name
, ".") || !strcmp(attr
->name
, ".."));
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;
365 case SSH_FILEXFER_TYPE_REGULAR
:
366 entry
->type
= AVIO_ENTRY_FILE
;
368 case SSH_FILEXFER_TYPE_DIRECTORY
:
369 entry
->type
= AVIO_ENTRY_DIRECTORY
;
371 case SSH_FILEXFER_TYPE_SYMLINK
:
372 entry
->type
= AVIO_ENTRY_SYMBOLIC_LINK
;
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
:
379 entry
->type
= AVIO_ENTRY_UNKNOWN
;
381 sftp_attributes_free(attr
);
385 static int libssh_close_dir(URLContext
*h
)
387 LIBSSHContext
*libssh
= h
->priv_data
;
389 sftp_closedir(libssh
->dir
);
395 static int libssh_delete(URLContext
*h
)
398 LIBSSHContext
*libssh
= h
->priv_data
;
399 sftp_attributes attr
= NULL
;
400 char path
[MAX_URL_SIZE
];
402 if ((ret
= libssh_connect(h
, h
->filename
, path
, sizeof(path
))) < 0)
405 if (!(attr
= sftp_stat(libssh
->sftp
, path
))) {
406 ret
= AVERROR(sftp_get_error(libssh
->sftp
));
410 if (attr
->type
== SSH_FILEXFER_TYPE_DIRECTORY
) {
411 if (sftp_rmdir(libssh
->sftp
, path
) < 0) {
412 ret
= AVERROR(sftp_get_error(libssh
->sftp
));
416 if (sftp_unlink(libssh
->sftp
, path
) < 0) {
417 ret
= AVERROR(sftp_get_error(libssh
->sftp
));
426 sftp_attributes_free(attr
);
431 static int libssh_move(URLContext
*h_src
, URLContext
*h_dst
)
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;
440 av_url_split(NULL
, 0,
441 credentials_src
, sizeof(credentials_src
),
442 hostname_src
, sizeof(hostname_src
),
444 path_src
, sizeof(path_src
),
447 av_url_split(NULL
, 0,
448 credentials_dst
, sizeof(credentials_dst
),
449 hostname_dst
, sizeof(hostname_dst
),
451 path_dst
, sizeof(path_dst
),
454 if (strcmp(credentials_src
, credentials_dst
) ||
455 strcmp(hostname_src
, hostname_dst
) ||
456 port_src
!= port_dst
) {
457 return AVERROR(EINVAL
);
460 if ((ret
= libssh_connect(h_src
, h_src
->filename
, path_src
, sizeof(path_src
))) < 0)
463 if (sftp_rename(libssh
->sftp
, path_src
, path_dst
) < 0) {
464 ret
= AVERROR(sftp_get_error(libssh
->sftp
));
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
},
485 static const AVClass libssh_context_class
= {
486 .class_name
= "libssh",
487 .item_name
= av_default_item_name
,
489 .version
= LIBAVUTIL_VERSION_INT
,
492 const URLProtocol ff_libssh_protocol
= {
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
,