Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | RSS feed

  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.  
  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.  
  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 && (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.  
  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.  
  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.  
  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.  
  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.  
  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.  
  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.  
  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.  
  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;
  299. }
  300.  
  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.  
  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.  
  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.  
  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.  
  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.  
  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. 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.