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"
42 static av_cold
int libssh_create_ssh_session(LIBSSHContext
*libssh
, const char* hostname
, unsigned int port
)
44 static const int verbosity
= SSH_LOG_NOLOG
;
46 if (!(libssh
->session
= ssh_new())) {
47 av_log(libssh
, AV_LOG_ERROR
, "SSH session creation failed: %s\n", ssh_get_error(libssh
->session
));
48 return AVERROR(ENOMEM
);
50 ssh_options_set(libssh
->session
, SSH_OPTIONS_HOST
, hostname
);
51 ssh_options_set(libssh
->session
, SSH_OPTIONS_PORT
, &port
);
52 ssh_options_set(libssh
->session
, SSH_OPTIONS_LOG_VERBOSITY
, &verbosity
);
53 if (libssh
->rw_timeout
> 0) {
54 long timeout
= libssh
->rw_timeout
* 1000;
55 ssh_options_set(libssh
->session
, SSH_OPTIONS_TIMEOUT_USEC
, &timeout
);
58 if (ssh_connect(libssh
->session
) != SSH_OK
) {
59 av_log(libssh
, AV_LOG_ERROR
, "Connection failed: %s\n", ssh_get_error(libssh
->session
));
66 static av_cold
int libssh_authentication(LIBSSHContext
*libssh
, const char *user
, const char *password
)
72 ssh_options_set(libssh
->session
, SSH_OPTIONS_USER
, user
);
74 if (ssh_userauth_none(libssh
->session
, NULL
) == SSH_AUTH_SUCCESS
)
77 auth_methods
= ssh_userauth_list(libssh
->session
, NULL
);
79 if (auth_methods
& SSH_AUTH_METHOD_PUBLICKEY
) {
80 if (libssh
->priv_key
) {
82 ssh_private_key priv_key
;
84 if (!ssh_try_publickey_from_file(libssh
->session
, libssh
->priv_key
, &pub_key
, &type
)) {
85 priv_key
= privatekey_from_file(libssh
->session
, libssh
->priv_key
, type
, password
);
86 if (ssh_userauth_pubkey(libssh
->session
, NULL
, pub_key
, priv_key
) == SSH_AUTH_SUCCESS
) {
87 av_log(libssh
, AV_LOG_DEBUG
, "Authentication successful with selected private key.\n");
91 av_log(libssh
, AV_LOG_DEBUG
, "Invalid key is provided.\n");
92 return AVERROR(EACCES
);
94 } else if (ssh_userauth_autopubkey(libssh
->session
, password
) == SSH_AUTH_SUCCESS
) {
95 av_log(libssh
, AV_LOG_DEBUG
, "Authentication successful with auto selected key.\n");
100 if (!authorized
&& (auth_methods
& SSH_AUTH_METHOD_PASSWORD
)) {
101 if (ssh_userauth_password(libssh
->session
, NULL
, password
) == SSH_AUTH_SUCCESS
) {
102 av_log(libssh
, AV_LOG_DEBUG
, "Authentication successful with password.\n");
108 av_log(libssh
, AV_LOG_ERROR
, "Authentication failed.\n");
109 return AVERROR(EACCES
);
115 static av_cold
int libssh_create_sftp_session(LIBSSHContext
*libssh
)
117 if (!(libssh
->sftp
= sftp_new(libssh
->session
))) {
118 av_log(libssh
, AV_LOG_ERROR
, "SFTP session creation failed: %s\n", ssh_get_error(libssh
->session
));
119 return AVERROR(ENOMEM
);
122 if (sftp_init(libssh
->sftp
) != SSH_OK
) {
123 av_log(libssh
, AV_LOG_ERROR
, "Error initializing sftp session: %s\n", ssh_get_error(libssh
->session
));
130 static av_cold
int libssh_open_file(LIBSSHContext
*libssh
, int flags
, const char *file
)
134 if ((flags
& AVIO_FLAG_WRITE
) && (flags
& AVIO_FLAG_READ
)) {
135 access
= O_CREAT
| O_RDWR
;
138 } else if (flags
& AVIO_FLAG_WRITE
) {
139 access
= O_CREAT
| O_WRONLY
;
145 /* 0666 = -rw-rw-rw- = read+write for everyone, minus umask */
146 if (!(libssh
->file
= sftp_open(libssh
->sftp
, file
, access
, 0666))) {
147 av_log(libssh
, AV_LOG_ERROR
, "Error opening sftp file: %s\n", ssh_get_error(libssh
->session
));
154 static av_cold
void libssh_stat_file(LIBSSHContext
*libssh
)
156 sftp_attributes stat
;
158 if (!(stat
= sftp_fstat(libssh
->file
))) {
159 av_log(libssh
, AV_LOG_WARNING
, "Cannot stat remote file.\n");
160 libssh
->filesize
= -1;
162 libssh
->filesize
= stat
->size
;
163 sftp_attributes_free(stat
);
167 static av_cold
int libssh_close(URLContext
*h
)
169 LIBSSHContext
*libssh
= h
->priv_data
;
171 sftp_close(libssh
->file
);
175 sftp_free(libssh
->sftp
);
178 if (libssh
->session
) {
179 ssh_disconnect(libssh
->session
);
180 ssh_free(libssh
->session
);
181 libssh
->session
= NULL
;
186 static av_cold
int libssh_open(URLContext
*h
, const char *url
, int flags
)
188 LIBSSHContext
*libssh
= h
->priv_data
;
189 char proto
[10], path
[MAX_URL_SIZE
], hostname
[1024], credencials
[1024];
191 const char *user
= NULL
, *pass
= NULL
;
194 av_url_split(proto
, sizeof(proto
),
195 credencials
, sizeof(credencials
),
196 hostname
, sizeof(hostname
),
201 if (port
<= 0 || port
> 65535)
204 if ((ret
= libssh_create_ssh_session(libssh
, hostname
, port
)) < 0)
207 user
= av_strtok(credencials
, ":", &end
);
208 pass
= av_strtok(end
, ":", &end
);
210 if ((ret
= libssh_authentication(libssh
, user
, pass
)) < 0)
213 if ((ret
= libssh_create_sftp_session(libssh
)) < 0)
216 if ((ret
= libssh_open_file(libssh
, flags
, path
)) < 0)
219 libssh_stat_file(libssh
);
228 static int64_t libssh_seek(URLContext
*h
, int64_t pos
, int whence
)
230 LIBSSHContext
*libssh
= h
->priv_data
;
233 if (libssh
->filesize
== -1 && (whence
== AVSEEK_SIZE
|| whence
== SEEK_END
)) {
234 av_log(h
, AV_LOG_ERROR
, "Error during seeking.\n");
240 return libssh
->filesize
;
245 newpos
= sftp_tell64(libssh
->file
) + pos
;
248 newpos
= libssh
->filesize
+ pos
;
251 return AVERROR(EINVAL
);
255 av_log(h
, AV_LOG_ERROR
, "Seeking to nagative position.\n");
256 return AVERROR(EINVAL
);
259 if (sftp_seek64(libssh
->file
, newpos
)) {
260 av_log(h
, AV_LOG_ERROR
, "Error during seeking.\n");
267 static int libssh_read(URLContext
*h
, unsigned char *buf
, int size
)
269 LIBSSHContext
*libssh
= h
->priv_data
;
272 if ((bytes_read
= sftp_read(libssh
->file
, buf
, size
)) < 0) {
273 av_log(libssh
, AV_LOG_ERROR
, "Read error.\n");
279 static int libssh_write(URLContext
*h
, const unsigned char *buf
, int size
)
281 LIBSSHContext
*libssh
= h
->priv_data
;
284 if ((bytes_written
= sftp_write(libssh
->file
, buf
, size
)) < 0) {
285 av_log(libssh
, AV_LOG_ERROR
, "Write error.\n");
288 return bytes_written
;
291 #define OFFSET(x) offsetof(LIBSSHContext, x)
292 #define D AV_OPT_FLAG_DECODING_PARAM
293 #define E AV_OPT_FLAG_ENCODING_PARAM
294 static const AVOption options
[] = {
295 {"timeout", "set timeout of socket I/O operations", OFFSET(rw_timeout
), AV_OPT_TYPE_INT
, {.i64
= -1}, -1, INT_MAX
, D
|E
},
296 {"truncate", "Truncate existing files on write", OFFSET(trunc
), AV_OPT_TYPE_INT
, { .i64
= 1 }, 0, 1, E
},
297 {"private_key", "set path to private key", OFFSET(priv_key
), AV_OPT_TYPE_STRING
, { .str
= NULL
}, 0, 0, D
|E
},
301 static const AVClass libssh_context_class
= {
302 .class_name
= "libssh",
303 .item_name
= av_default_item_name
,
305 .version
= LIBAVUTIL_VERSION_INT
,
308 URLProtocol ff_libssh_protocol
= {
310 .url_open
= libssh_open
,
311 .url_read
= libssh_read
,
312 .url_write
= libssh_write
,
313 .url_seek
= libssh_seek
,
314 .url_close
= libssh_close
,
315 .priv_data_size
= sizeof(LIBSSHContext
),
316 .priv_data_class
= &libssh_context_class
,
317 .flags
= URL_PROTOCOL_FLAG_NETWORK
,