Commit | Line | Data |
---|---|---|
2ba45a60 DM |
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 "avformat.h" | |
28 | #include "internal.h" | |
29 | #include "url.h" | |
30 | ||
31 | typedef struct { | |
32 | const AVClass *class; | |
33 | ssh_session session; | |
34 | sftp_session sftp; | |
35 | sftp_file file; | |
36 | int64_t filesize; | |
37 | int rw_timeout; | |
38 | int trunc; | |
39 | char *priv_key; | |
40 | } LIBSSHContext; | |
41 | ||
42 | static av_cold int libssh_create_ssh_session(LIBSSHContext *libssh, const char* hostname, unsigned int port) | |
43 | { | |
44 | static const int verbosity = SSH_LOG_NOLOG; | |
45 | ||
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); | |
49 | } | |
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); | |
56 | } | |
57 | ||
58 | if (ssh_connect(libssh->session) != SSH_OK) { | |
59 | av_log(libssh, AV_LOG_ERROR, "Connection failed: %s\n", ssh_get_error(libssh->session)); | |
60 | return AVERROR(EIO); | |
61 | } | |
62 | ||
63 | return 0; | |
64 | } | |
65 | ||
66 | static av_cold int libssh_authentication(LIBSSHContext *libssh, const char *user, const char *password) | |
67 | { | |
68 | int authorized = 0; | |
69 | int auth_methods; | |
70 | ||
71 | if (user) | |
72 | ssh_options_set(libssh->session, SSH_OPTIONS_USER, user); | |
73 | ||
74 | if (ssh_userauth_none(libssh->session, NULL) == SSH_AUTH_SUCCESS) | |
75 | return 0; | |
76 | ||
77 | auth_methods = ssh_userauth_list(libssh->session, NULL); | |
78 | ||
79 | if (auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { | |
80 | if (libssh->priv_key) { | |
81 | ssh_string pub_key; | |
82 | ssh_private_key priv_key; | |
83 | int type; | |
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"); | |
88 | authorized = 1; | |
89 | } | |
90 | } else { | |
91 | av_log(libssh, AV_LOG_DEBUG, "Invalid key is provided.\n"); | |
92 | return AVERROR(EACCES); | |
93 | } | |
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"); | |
96 | authorized = 1; | |
97 | } | |
98 | } | |
99 | ||
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"); | |
103 | authorized = 1; | |
104 | } | |
105 | } | |
106 | ||
107 | if (!authorized) { | |
108 | av_log(libssh, AV_LOG_ERROR, "Authentication failed.\n"); | |
109 | return AVERROR(EACCES); | |
110 | } | |
111 | ||
112 | return 0; | |
113 | } | |
114 | ||
115 | static av_cold int libssh_create_sftp_session(LIBSSHContext *libssh) | |
116 | { | |
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); | |
120 | } | |
121 | ||
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)); | |
124 | return AVERROR(EIO); | |
125 | } | |
126 | ||
127 | return 0; | |
128 | } | |
129 | ||
130 | static av_cold int libssh_open_file(LIBSSHContext *libssh, int flags, const char *file) | |
131 | { | |
132 | int access; | |
133 | ||
134 | if ((flags & AVIO_FLAG_WRITE) && (flags & AVIO_FLAG_READ)) { | |
135 | access = O_CREAT | O_RDWR; | |
136 | if (libssh->trunc) | |
137 | access |= O_TRUNC; | |
138 | } else if (flags & AVIO_FLAG_WRITE) { | |
139 | access = O_CREAT | O_WRONLY; | |
140 | if (libssh->trunc) | |
141 | access |= O_TRUNC; | |
142 | } else | |
143 | access = O_RDONLY; | |
144 | ||
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)); | |
148 | return AVERROR(EIO); | |
149 | } | |
150 | ||
151 | return 0; | |
152 | } | |
153 | ||
154 | static av_cold void libssh_stat_file(LIBSSHContext *libssh) | |
155 | { | |
156 | sftp_attributes stat; | |
157 | ||
158 | if (!(stat = sftp_fstat(libssh->file))) { | |
159 | av_log(libssh, AV_LOG_WARNING, "Cannot stat remote file.\n"); | |
160 | libssh->filesize = -1; | |
161 | } else { | |
162 | libssh->filesize = stat->size; | |
163 | sftp_attributes_free(stat); | |
164 | } | |
165 | } | |
166 | ||
167 | static av_cold int libssh_close(URLContext *h) | |
168 | { | |
169 | LIBSSHContext *libssh = h->priv_data; | |
170 | if (libssh->file) { | |
171 | sftp_close(libssh->file); | |
172 | libssh->file = NULL; | |
173 | } | |
174 | if (libssh->sftp) { | |
175 | sftp_free(libssh->sftp); | |
176 | libssh->sftp = NULL; | |
177 | } | |
178 | if (libssh->session) { | |
179 | ssh_disconnect(libssh->session); | |
180 | ssh_free(libssh->session); | |
181 | libssh->session = NULL; | |
182 | } | |
183 | return 0; | |
184 | } | |
185 | ||
186 | static av_cold int libssh_open(URLContext *h, const char *url, int flags) | |
187 | { | |
188 | LIBSSHContext *libssh = h->priv_data; | |
189 | char proto[10], path[MAX_URL_SIZE], hostname[1024], credencials[1024]; | |
190 | int port = 22, ret; | |
191 | const char *user = NULL, *pass = NULL; | |
192 | char *end = NULL; | |
193 | ||
194 | av_url_split(proto, sizeof(proto), | |
195 | credencials, sizeof(credencials), | |
196 | hostname, sizeof(hostname), | |
197 | &port, | |
198 | path, sizeof(path), | |
199 | url); | |
200 | ||
201 | if (port <= 0 || port > 65535) | |
202 | port = 22; | |
203 | ||
204 | if ((ret = libssh_create_ssh_session(libssh, hostname, port)) < 0) | |
205 | goto fail; | |
206 | ||
207 | user = av_strtok(credencials, ":", &end); | |
208 | pass = av_strtok(end, ":", &end); | |
209 | ||
210 | if ((ret = libssh_authentication(libssh, user, pass)) < 0) | |
211 | goto fail; | |
212 | ||
213 | if ((ret = libssh_create_sftp_session(libssh)) < 0) | |
214 | goto fail; | |
215 | ||
216 | if ((ret = libssh_open_file(libssh, flags, path)) < 0) | |
217 | goto fail; | |
218 | ||
219 | libssh_stat_file(libssh); | |
220 | ||
221 | return 0; | |
222 | ||
223 | fail: | |
224 | libssh_close(h); | |
225 | return ret; | |
226 | } | |
227 | ||
228 | static int64_t libssh_seek(URLContext *h, int64_t pos, int whence) | |
229 | { | |
230 | LIBSSHContext *libssh = h->priv_data; | |
231 | int64_t newpos; | |
232 | ||
233 | if (libssh->filesize == -1 && (whence == AVSEEK_SIZE || whence == SEEK_END)) { | |
234 | av_log(h, AV_LOG_ERROR, "Error during seeking.\n"); | |
235 | return AVERROR(EIO); | |
236 | } | |
237 | ||
238 | switch(whence) { | |
239 | case AVSEEK_SIZE: | |
240 | return libssh->filesize; | |
241 | case SEEK_SET: | |
242 | newpos = pos; | |
243 | break; | |
244 | case SEEK_CUR: | |
245 | newpos = sftp_tell64(libssh->file) + pos; | |
246 | break; | |
247 | case SEEK_END: | |
248 | newpos = libssh->filesize + pos; | |
249 | break; | |
250 | default: | |
251 | return AVERROR(EINVAL); | |
252 | } | |
253 | ||
254 | if (newpos < 0) { | |
255 | av_log(h, AV_LOG_ERROR, "Seeking to nagative position.\n"); | |
256 | return AVERROR(EINVAL); | |
257 | } | |
258 | ||
259 | if (sftp_seek64(libssh->file, newpos)) { | |
260 | av_log(h, AV_LOG_ERROR, "Error during seeking.\n"); | |
261 | return AVERROR(EIO); | |
262 | } | |
263 | ||
264 | return newpos; | |
265 | } | |
266 | ||
267 | static int libssh_read(URLContext *h, unsigned char *buf, int size) | |
268 | { | |
269 | LIBSSHContext *libssh = h->priv_data; | |
270 | int bytes_read; | |
271 | ||
272 | if ((bytes_read = sftp_read(libssh->file, buf, size)) < 0) { | |
273 | av_log(libssh, AV_LOG_ERROR, "Read error.\n"); | |
274 | return AVERROR(EIO); | |
275 | } | |
276 | return bytes_read; | |
277 | } | |
278 | ||
279 | static int libssh_write(URLContext *h, const unsigned char *buf, int size) | |
280 | { | |
281 | LIBSSHContext *libssh = h->priv_data; | |
282 | int bytes_written; | |
283 | ||
284 | if ((bytes_written = sftp_write(libssh->file, buf, size)) < 0) { | |
285 | av_log(libssh, AV_LOG_ERROR, "Write error.\n"); | |
286 | return AVERROR(EIO); | |
287 | } | |
288 | return bytes_written; | |
289 | } | |
290 | ||
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 }, | |
298 | {NULL} | |
299 | }; | |
300 | ||
301 | static const AVClass libssh_context_class = { | |
302 | .class_name = "libssh", | |
303 | .item_name = av_default_item_name, | |
304 | .option = options, | |
305 | .version = LIBAVUTIL_VERSION_INT, | |
306 | }; | |
307 | ||
308 | URLProtocol ff_libssh_protocol = { | |
309 | .name = "sftp", | |
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, | |
318 | }; |