Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/VLC/modules/access/   (VLC Media Plyer ©)  Datei vom 23.9.2026 mit Größe 45 kB image not shown  

Quelle  ftp.c   Sprache: C

 

/*****************************************************************************
 * ftp.c: FTP input module
 *****************************************************************************
 * Copyright (C) 2001-2006 VLC authors and VideoLAN
 * Copyright © 2006 Rémi Denis-Courmont
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr> - original code
 *          Rémi Denis-Courmont - EPSV support
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/


/*****************************************************************************
 * Preamble
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <assert.h>
#include <stdint.h>
#include <errno.h>
#include <ctype.h>

#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_access.h>
#include <vlc_dialog.h>
#include <vlc_input_item.h>
#include <vlc_memstream.h>
#include <vlc_network.h>
#include <vlc_url.h>
#include <vlc_tls.h>
#include <vlc_sout.h>
#include <vlc_charset.h>
#include <vlc_interrupt.h>
#include <vlc_keystore.h>
#include <vlc_strings.h>

#ifndef IPPORT_FTP
# define IPPORT_FTP 21u
#endif

#ifndef IPPORT_FTPS
# define IPPORT_FTPS 990u
#endif

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/

static int   InOpen ( vlc_object_t * );
static void  InClose( vlc_object_t * );
#ifdef ENABLE_SOUT
static int  OutOpen ( vlc_object_t * );
static void OutClose( vlc_object_t * );
#endif

#define USER_TEXT N_("Username")
#define USER_LONGTEXT N_("Username that will be used for the connection, " \
        "if no username is set in the URL.")
#define PASS_TEXT N_("Password")
#define PASS_LONGTEXT N_("Password that will be used for the connection, " \
        "if no username or password are set in URL.")
#define ACCOUNT_TEXT N_("FTP account")
#define ACCOUNT_LONGTEXT N_("Account that will be " \
    "used for the connection.")

#define LOGIN_DIALOG_TITLE _("FTP authentication")
#define LOGIN_DIALOG_TEXT _("Please enter a valid login and password for " \
        "the FTP connection to %s")

vlc_module_begin ()
    set_shortname( "FTP" )
    set_description( N_("FTP input") )
    set_capability( "access", 0 )
    set_subcategory( SUBCAT_INPUT_ACCESS )
    add_string( "ftp-user", NULL, USER_TEXT, USER_LONGTEXT )
    add_password( "ftp-pwd", NULL, PASS_TEXT, PASS_LONGTEXT )
    add_string( "ftp-account", "anonymous", ACCOUNT_TEXT,
                ACCOUNT_LONGTEXT )
    add_shortcut( "ftp", "ftps", "ftpes" )
    set_callbacks( InOpen, InClose )

#ifdef ENABLE_SOUT
    add_submodule ()
        set_shortname( "FTP" )
        set_description( N_("FTP upload output") )
        set_capability( "sout access", 0 )
        set_subcategory( SUBCAT_SOUT_ACO )
        add_shortcut( "ftp", "ftps", "ftpes" )
        set_callbacks( OutOpen, OutClose )
#endif
vlc_module_end ()

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/


typedef struct access_sys_t access_sys_t;

static ssize_t Read( stream_t *, void *, size_t );
static int Seek( stream_t *, uint64_t );
static int Control( stream_t *, int, va_list );
static int DirRead( stream_t *, input_item_node_t * );
#ifdef ENABLE_SOUT
static int OutSeek( sout_access_out_t *, uint64_t );
static ssize_t Write( sout_access_out_t *, block_t * );
#endif

static int LoginUserPwd( vlc_object_t *, access_sys_t *,
                         const char *, const char *, bool * );
static void FeaturesCheck( void *, const char * );

typedef struct ftp_features_t
{
    bool b_unicode;
    bool b_authtls;
    bool b_mlst;
    bool b_list;
} ftp_features_t;

enum tls_mode_e
{
    NONE = 0,
    IMPLICIT,/* ftps */
    EXPLICIT /* ftpes */
};

struct access_sys_t
{
    vlc_url_t  url;

    ftp_features_t   features;
    vlc_tls_client_t *p_creds;
    enum tls_mode_e  tlsmode;
    vlc_tls_t *cmd;
    vlc_tls_t *data;

    char       sz_epsv_ip[NI_MAXNUMERICHOST];
    bool       out;
    uint64_t   offset;
    uint64_t   size;
};
#define GET_OUT_SYS( p_this ) \
    ((access_sys_t *)(((sout_access_out_t *)(p_this))->p_sys))

VLC_FORMAT(3, 4)
static int ftp_SendCommand( vlc_object_t *obj, access_sys_t *sys,
                            const char *fmt, ... )
{
    size_t fmtlen = strlen( fmt );
    char fmtbuf[fmtlen + 3];

    memcpy( fmtbuf, fmt, fmtlen );
    memcpy( fmtbuf + fmtlen, "\r\n", 3 );

    va_list args;
    char *cmd;
    int val;

    va_start( args, fmt );
    val = vasprintf( &cmd, fmtbuf, args );
    va_end( args );
    if( unlikely(val == -1) )
        return -1;

    if( strncmp( cmd, "PASS ", 5 ) && strncmp( cmd, "ACCT ", 5 ) )
        msg_Dbg( obj, "sending request: \"%.*s\" (%d bytes)", val-2, cmd, val );
    else
        msg_Dbg( obj, "sending request: \"%.*s XXXX\" (XX bytes)", 4, cmd );

    if( vlc_tls_Write( sys->cmd, cmd, val ) != val )
    {
        msg_Err( obj, "request failure" );
        val = -1;
    }
    else
        val = 0;
    free( cmd );
    return val;
}

static char *ftp_GetLine( vlc_object_t *obj, access_sys_t *sys )
{
    char *resp = vlc_tls_GetLine( sys->cmd );
    if( resp == NULL )
        msg_Err( obj, "response failure" );
    return resp;
}

/* TODO support this s**t :
 RFC 959 allows the client to send certain TELNET strings at any moment,
 even in the middle of a request:

 * \377\377.
 * \377\376x where x is one byte.
 * \377\375x where x is one byte. The server is obliged to send \377\374x
 *                                immediately after reading x.
 * \377\374x where x is one byte.
 * \377\373x where x is one byte. The server is obliged to send \377\376x
 *                                immediately after reading x.
 * \377x for any other byte x.

 These strings are not part of the requests, except in the case \377\377,
 where the request contains one \377. */

static int ftp_RecvReply( vlc_object_t *obj, access_sys_t *sys,
                          char **restrict strp,
                          void (*cb)(void *, const char *), void *opaque )
{
    char *resp = ftp_GetLine( obj, sys );
    if( resp == NULL )
        return -1;

    char *end;
    unsigned code = strtoul( resp, &end, 10 );
    if( (end - resp) != 3 || (*end != '-' && *end != ' ') )
    {
        msg_Err( obj, "malformatted response" );
        goto error;
    }
    msg_Dbg( obj, "received response: \"%s\"", resp );

    if( *end == '-' ) /* Multi-line response */
    {
        bool done;

        *end = ' ';
        do
        {
            char *line = ftp_GetLine( obj, sys );
            if( line == NULL )
                goto error;

            done = !strncmp( resp, line, 4 );
            if( !done )
                cb( opaque, line );
            free( line );
        }
        while( !done );
    }

    if( strp != NULL )
        *strp = resp;
    else
        free( resp );
    return code;
error:
    free( resp );
    return -1;
}

static int ftp_RecvAnswer( vlc_object_t *obj, access_sys_t *sys,
                           int *restrict codep, char **restrict strp,
                           void (*cb)(void *, const char *), void *opaque )
{
    char *str;
    int val = ftp_RecvReply( obj, sys, &str, cb, opaque );
    if( (val / 100) == 1 )
    {   /* There can be zero or one preliminary reply per command */
        free( str );
        val = ftp_RecvReply( obj, sys, &str, cb, opaque );
    }

    if( val >= 0 )
    {
        if( codep != NULL )
            *codep = val;
        if( strp != NULL )
            *strp = str;
        else
            free( str );
        val /= 100;
    }
    else
    {
        if( codep != NULL )
            *codep = 500;
        if( strp != NULL )
            *strp = NULL;
    }
    return val;
}

static void DummyLine( void *data, const char *str )
{
    (void) data; (void) str;
}

static int ftp_RecvCommand( vlc_object_t *obj, access_sys_t *sys,
                            int *restrict codep, char **restrict strp )
{
    return ftp_RecvAnswer( obj, sys, codep, strp, DummyLine, NULL );
}

static int ftp_RecvCommandInit( vlc_object_t *obj, access_sys_t *sys )
{
    int val = ftp_RecvReply( obj, sys, NULL, DummyLine, NULL );
    if( val >= 0 )
        val /= 100;
    return val;
}

static int ftp_StartStream( vlc_object_t *, access_sys_t *, uint64_t, bool );
static int ftp_StopStream ( vlc_object_t *, access_sys_t * );

static int readTLSMode( vlc_object_t *obj, access_sys_t *p_sys,
                        const char * psz_access )
{
    if ( !strncmp( psz_access, "ftps", 4 ) )
        p_sys->tlsmode = IMPLICIT;
    else
    if ( !strncmp( psz_access, "ftpes", 5 ) )
        p_sys->tlsmode = EXPLICIT;
    else
    {
        p_sys->p_creds = NULL;
        p_sys->tlsmode = NONE;
        return 0;
    }

    p_sys->p_creds = vlc_tls_ClientCreate( obj );
    return (p_sys->p_creds != NULL) ? 0 : -1;
}

static int createCmdTLS( vlc_object_t *p_access, access_sys_t *p_sys,
                         const char *psz_session_name )
{
    /* TLS/SSL handshake */
    vlc_tls_t *secure = vlc_tls_ClientSessionCreate( p_sys->p_creds,
                                                     p_sys->cmd,
                                                     p_sys->url.psz_host,
                                                     psz_session_name,
                                                     NULL, NULL );
    if( secure == NULL )
    {
        msg_Err( p_access, "cannot establish FTP/TLS session on command channel" );
        return -1;
    }
    p_sys->cmd = secure;
    return 0;
}

static void clearCmd( access_sys_t *p_sys )
{
    if( p_sys->cmd != NULL )
    {
        vlc_tls_Close( p_sys->cmd );
        p_sys->cmd = NULL;
    }
}

/* Sanitize for the protocol rfc1738. epscially \n\r */
static bool HasControlChar( const char *p )
{
    if( p )
    {
        for( ; *p; p++ )
        {
            unsigned char c = (unsigned char)*p;
            if( (c < 0x20 && c != '\t') || c == 0x7F || c == 0xFF /* IAC control byte */ )
                return true;
        }
    }
    return false;
}

static int Login( vlc_object_t *p_access, access_sys_t *p_sys, const char *path )
{
    int i_answer;

    /* *** Open a TCP connection with server *** */
    p_sys->cmd = vlc_tls_SocketOpenTCP( p_access, p_sys->url.psz_host,
                                        p_sys->url.i_port );
    if( p_sys->cmd == NULL )
    {
        msg_Err( p_access, "connection failed" );
        vlc_dialog_display_error( p_access, _("Network interaction failed"), "%s",
            _("VLC could not connect with the given server.") );
        goto error;
    }

    if ( p_sys->tlsmode == IMPLICIT ) /* FTPS Mode */
    {
        if ( createCmdTLS( p_access, p_sys, "ftps") < 0 )
            goto error;
    }

    while( ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) == 1 );

    if( i_answer / 100 != 2 )
    {
        msg_Err( p_access, "connection rejected" );
        vlc_dialog_display_error( p_access, _("Network interaction failed"), "%s",
            _("VLC's connection to the given server was rejected.") );
        goto error;
    }

    msg_Dbg( p_access, "connection accepted (%d)", i_answer );

    /* Features check first */
    if( ftp_SendCommand( p_access, p_sys, "FEAT" ) < 0
     || ftp_RecvAnswer( p_access, p_sys, NULL, NULL,
                        FeaturesCheck, &p_sys->features ) < 0 )
    {
         msg_Err( p_access, "cannot get server features" );
         goto error;
    }

    /* Create TLS Session */
    if( p_sys->tlsmode == EXPLICIT )
    {
        if ( ! p_sys->features.b_authtls )
        {
            msg_Err( p_access, "Server does not support TLS" );
            goto error;
        }

        if( ftp_SendCommand( p_access, p_sys, "AUTH TLS" ) < 0
         || ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0
         || i_answer != 234 )
        {
             msg_Err( p_access, "cannot switch to TLS: server replied with code %d",
                      i_answer );
             goto error;
        }

        if( createCmdTLS( p_access, p_sys, "ftpes") < 0 )
        {
            goto error;
        }
    }

    if( p_sys->tlsmode != NONE )
    {
        if( ftp_SendCommand( p_access, p_sys, "PBSZ 0" ) < 0 ||
            ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 ||
            i_answer != 200 )
        {
            msg_Err( p_access, "Can't truncate Protection buffer size for TLS" );
            goto error;
        }

        if( ftp_SendCommand( p_access, p_sys, "PROT P" ) < 0 ||
            ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 ||
            i_answer != 200 )
        {
            msg_Err( p_access, "Can't set Data channel protection" );
            goto error;
        }
    }

    vlc_url_t url;
    vlc_credential credential;
    if( vlc_UrlParseFixup( &url, path ) != 0 )
    {
        vlc_UrlClean( &url );
        goto error;
    }
    vlc_credential_init( &credential, &url );
    bool b_logged = false;

    /* First: try credentials from url / option */
    if (vlc_credential_get( &credential, p_access, "ftp-user", "ftp-pwd",
                            NULL, NULL ) == -EINTR )
    {
        vlc_credential_clean( &credential );
        vlc_UrlClean( &url );
        goto error;
    }

    do
    {
        const char *psz_username = credential.psz_username;

        if( HasControlChar( credential.psz_username ) ||
            HasControlChar( credential.psz_password ) )
        {
            msg_Err( p_access, "Invalid chars in the username/password" );
            break;
        }

        if( psz_username == NULL ) /* use anonymous by default */
            psz_username = "anonymous";

        if( LoginUserPwd( p_access, p_sys, psz_username,
                          credential.psz_password, &b_logged ) != 0
         || b_logged )
            break;
    }
    while( vlc_credential_get( &credential, p_access, "ftp-user", "ftp-pwd",
                               LOGIN_DIALOG_TITLE, LOGIN_DIALOG_TEXT,
                               url.psz_host ) == 0 );

    if( b_logged )
    {
        vlc_credential_store( &credential, p_access );
        vlc_credential_clean( &credential );
        vlc_UrlClean( &url );
        return 0;
    }
    vlc_credential_clean( &credential );
    vlc_UrlClean( &url );
error:
    clearCmd( p_sys );
    return -1;
}

static int LoginUserPwd( vlc_object_t *p_access, access_sys_t *p_sys,
                         const char *psz_user, const char *psz_pwd,
                         bool *p_logged )
{
    int i_answer;

    /* Send credentials over channel */
    if( ftp_SendCommand( p_access, p_sys, "USER %s", psz_user ) < 0 ||
        ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
        return -1;

    switch( i_answer / 100 )
    {
        case 2:
            /* X.509 auth successful after AUTH TLS / RFC 2228 sec. 4 */
            if ( i_answer == 232 )
                msg_Dbg( p_access, "user accepted and authenticated" );
            else
                msg_Dbg( p_access, "user accepted" );
            break;
        case 3:
            msg_Dbg( p_access, "password needed" );

            if( ftp_SendCommand( p_access, p_sys, "PASS %s", psz_pwd ) < 0 ||
                ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
                return -1;

            switch( i_answer / 100 )
            {
                case 2:
                    msg_Dbg( p_access, "password accepted" );
                    break;
                case 3:
                {
                    char *psz;
                    msg_Dbg( p_access, "account needed" );
                    psz = var_InheritString( p_access, "ftp-account" );
                    bool badchars = HasControlChar( psz );
                    if( badchars ||
                        ftp_SendCommand( p_access, p_sys, "ACCT %s",
                                         psz ) < 0 ||
                        ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) < 0 )
                    {
                        if( badchars )
                            msg_Err( p_access, "Invalid chars in the ftp-account" );
                        free( psz );
                        return -1;
                    }
                    free( psz );

                    if( i_answer / 100 != 2 )
                    {
                        msg_Err( p_access, "account rejected" );
                        vlc_dialog_display_error( p_access,
                          _("Network interaction failed"),
                          "%s", _("Your account was rejected.") );
                        return -1;
                    }
                    msg_Dbg( p_access, "account accepted" );
                    break;
                }

                default:
                    msg_Warn( p_access, "password rejected" );
                    *p_logged = false;
                    return 0;
            }
            break;
        default:
            msg_Warn( p_access, "user rejected" );
            *p_logged = false;
            return 0;
    }

    *p_logged = true;
    return 0;
}

static void FeaturesCheck( void *opaque, const char *feature )
{
    ftp_features_t *features = opaque;

    if( strcasestr( feature, "UTF8" ) != NULL )
        features->b_unicode = true;
    else
    if( strcasestr( feature, "AUTH TLS" ) != NULL )
        features->b_authtls = true;

    if( strcasestr( feature, "MLST" ) != NULL )
        features->b_mlst = true;
}

static int Connect( vlc_object_t *p_access, access_sys_t *p_sys, const char *path )
{
    if( Login( p_access, p_sys, path ) < 0 )
        return -1;

    /* Extended passive mode */
    if( ftp_SendCommand( p_access, p_sys, "EPSV ALL" ) < 0 )
    {
        msg_Err( p_access, "cannot request extended passive mode" );
        goto error;
    }

    if( ftp_RecvCommand( p_access, p_sys, NULL, NULL ) == 2 )
    {
        int fd = vlc_tls_GetFD(p_sys->cmd);
        if( net_GetPeerAddress( fd, p_sys->sz_epsv_ip, NULL ) )
            goto error;
    }
    else
    {
        /* If ESPV ALL fails, we fallback to PASV.
         * We have to restart the connection in case there is a NAT that
         * understands EPSV ALL in the way, and hence won't allow PASV on
         * the initial connection.
         */

        msg_Info( p_access, "FTP Extended passive mode disabled" );
        clearCmd( p_sys );

        if( Login( p_access, p_sys, path ) )
            goto error;
    }

    if( p_sys->url.psz_path &&
        (p_sys->features.b_unicode ? IsUTF8 : IsASCII)(p_sys->url.psz_path) == NULL )
    {
        msg_Err( p_access, "unsupported path: \"%s\"", p_sys->url.psz_path );
        goto error;
    }

    /* check binary mode support */
    if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
        ftp_RecvCommand( p_access, p_sys, NULL, NULL ) != 2 )
    {
        msg_Err( p_access, "cannot set binary transfer mode" );
        goto error;
    }

    return 0;

error:
    clearCmd( p_sys );
    return -1;
}


static int parseURL( vlc_object_t *p_obj, vlc_url_t *url, const char *path, enum tls_mode_e mode )
{
    if( path == NULL )
        return VLC_EGENERIC;

    /* *** Parse URL and get server addr/port and path *** */
    while( *path == '/' )
        path++;

    vlc_UrlParseFixup( url, path );

    if( url->psz_host == NULL || *url->psz_host == '\0' )
        return VLC_EGENERIC;

    if( url->i_port <= 0 )
    {
        if( mode == IMPLICIT )
            url->i_port = IPPORT_FTPS;
        else
            url->i_port = IPPORT_FTP; /* default port */
    }

    if( url->psz_path == NULL )
        return VLC_SUCCESS;
    /* FTP URLs are relative to user's default directory (RFC1738 §3.2)
    For absolute path use ftp://foo.bar//usr/local/etc/filename */

    /* FIXME: we should issue a series of CWD, one per slash */
    if( url->psz_path )
    {
        assert( url->psz_path[0] == '/' );
        url->psz_path++;
    }

    char *type = strstr( url->psz_path, ";type=" );
    if( type )
    {
        *type = '\0';
        if( strchr( "iI", type[6] ) == NULL )
            return VLC_EGENERIC; /* ASCII and directory not supported */
    }
    vlc_uri_decode( url->psz_path );
    if( HasControlChar( url->psz_path ) )
    {
        msg_Err( p_obj, "Invalid chars in the path" );
        return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}


/****************************************************************************
 * Open: connect to ftp server and ask for file
 ****************************************************************************/

static int InOpen( vlc_object_t *p_this )
{
    stream_t     *p_access = (stream_t*)p_this;
    access_sys_t *p_sys;
    char         *psz_arg;
    bool          b_directory;

    /* Init p_access */
    p_sys = p_access->p_sys = (access_sys_t*)vlc_obj_calloc( p_this, 1, sizeof( access_sys_t ) );
    if( !p_sys )
        return VLC_ENOMEM;
    p_sys->data = NULL;
    p_sys->out = false;
    p_sys->offset = 0;
    p_sys->size = UINT64_MAX;

    if( readTLSMode( p_this, p_sys, p_access->psz_name ) )
        goto exit_error;

    if( parseURL( p_this,&p_sys->url, p_access->psz_url, p_sys->tlsmode ) )
        goto exit_error;

    if( Connect( p_this, p_sys, p_access->psz_url ) )
        goto exit_error;

    do {
        /* get size */
        if( p_sys->url.psz_path == NULL || !*p_sys->url.psz_path )
        {
            b_directory = true;
            break;
        }

        if( ftp_SendCommand( p_this, p_sys, "SIZE %s",
                             p_sys->url.psz_path ) < 0 )
            goto error;

        int val = ftp_RecvCommand( p_this, p_sys, NULL, &psz_arg );
        if( val == 2 )
        {
            b_directory = false;
            p_sys->size = atoll( &psz_arg[4] );
            free( psz_arg );
            msg_Dbg( p_access, "file size: %"PRIu64, p_sys->size );
            break;
        }
        if( val >= 0 )
            free( psz_arg );

        if( ftp_SendCommand( p_this, p_sys, "CWD %s",
                             p_sys->url.psz_path ) < 0 )
            goto error;

        if( ftp_RecvCommand( p_this, p_sys, NULL, NULL ) == 2 )
        {
            b_directory = true;
            break;
        }

        msg_Err( p_this, "file or directory does not exist" );
        goto error;
    } while (0);

    if( b_directory )
    {
        p_access->pf_readdir = DirRead;
        p_access->pf_control = access_vaDirectoryControlHelper;
    } else
        ACCESS_SET_CALLBACKS( Read, NULL, Control, Seek ); \

    /* Start the 'stream' */
    if( ftp_StartStream( p_this, p_sys, 0, b_directory ) < 0 )
    {
        msg_Err( p_this, "cannot retrieve file" );
        goto error;
    }

    return VLC_SUCCESS;

error:
    clearCmd( p_sys );

exit_error:
    vlc_UrlClean( &p_sys->url );
    vlc_tls_ClientDelete( p_sys->p_creds );
    return VLC_EGENERIC;
}

#ifdef ENABLE_SOUT
static int OutOpen( vlc_object_t *p_this )
{
    sout_access_out_t *p_access = (sout_access_out_t *)p_this;
    access_sys_t      *p_sys;

    p_sys = vlc_obj_calloc( p_this, 1, sizeof( *p_sys ) );
    if( !p_sys )
        return VLC_ENOMEM;

    /* Init p_access */
    p_sys->data = NULL;
    p_sys->out = true;

    if( readTLSMode( p_this, p_sys, p_access->psz_access ) )
        goto exit_error;

    if( parseURL( p_this, &p_sys->url, p_access->psz_path, p_sys->tlsmode ) )
        goto exit_error;
    if( p_sys->url.psz_path == NULL )
    {
        msg_Err( p_this, "no filename specified" );
        goto exit_error;
    }

    if( Connect( p_this, p_sys, p_access->psz_path ) )
        goto exit_error;

    /* Start the 'stream' */
    if( ftp_StartStream( p_this, p_sys, 0, false ) < 0 )
    {
        msg_Err( p_access, "cannot store file" );
        clearCmd( p_sys );
        goto exit_error;
    }

    p_access->pf_seek = OutSeek;
    p_access->pf_write = Write;
    p_access->p_sys = (void *)p_sys;

    return VLC_SUCCESS;

exit_error:
    vlc_UrlClean( &p_sys->url );
    vlc_tls_ClientDelete( p_sys->p_creds );
    return VLC_EGENERIC;
}
#endif

/*****************************************************************************
 * Close: free unused data structures
 *****************************************************************************/

static void Close( vlc_object_t *p_access, access_sys_t *p_sys )
{
    msg_Dbg( p_access, "stopping stream" );
    ftp_StopStream( p_access, p_sys );

    if( ftp_SendCommand( p_access, p_sys, "QUIT" ) < 0 )
    {
        msg_Warn( p_access, "cannot quit" );
    }
    else
    {
        ftp_RecvCommand( p_access, p_sys, NULL, NULL );
    }

    clearCmd( p_sys );

    /* free memory */
    vlc_UrlClean( &p_sys->url );
    vlc_tls_ClientDelete( p_sys->p_creds );
}

static void InClose( vlc_object_t *p_this )
{
    Close( p_this, ((stream_t *)p_this)->p_sys);
}

#ifdef ENABLE_SOUT
static void OutClose( vlc_object_t *p_this )
{
    Close( p_this, GET_OUT_SYS(p_this));
}
#endif


/*****************************************************************************
 * Seek: try to go at the right place
 *****************************************************************************/

static int SeekCommon( vlc_object_t *p_access, access_sys_t *p_sys,
                       uint64_t i_pos )
{
    msg_Dbg( p_access, "seeking to %"PRIu64, i_pos );

    ftp_StopStream( p_access, p_sys );

    if( ftp_StartStream( p_access, p_sys, i_pos, false ) < 0 )
        return VLC_EGENERIC;
    return VLC_SUCCESS;
}

static int Seek( stream_t *p_access, uint64_t i_pos )
{
    access_sys_t *p_sys = p_access->p_sys;

    int val = SeekCommon( VLC_OBJECT(p_access), p_sys, i_pos );
    if( val )
        return val;

    p_sys->offset = i_pos;

    return VLC_SUCCESS;
}

#ifdef ENABLE_SOUT
static int OutSeek( sout_access_out_t *p_access, uint64_t i_pos )
{
    return SeekCommon(VLC_OBJECT(p_access), GET_OUT_SYS(p_access), i_pos);
}
#endif

/*****************************************************************************
 * Read:
 *****************************************************************************/

static ssize_t Read( stream_t *p_access, void *p_buffer, size_t i_len )
{
    access_sys_t *p_sys = p_access->p_sys;

    if( p_sys->data == NULL )
        return 0;
    assert( !p_sys->out );

    ssize_t i_read = vlc_tls_Read( p_sys->data, p_buffer, i_len, false );
    if( i_read >= 0 )
        p_sys->offset += i_read;
    else if( errno != EINTR && errno != EAGAIN )
    {
        msg_Err( p_access, "receive error: %s", vlc_strerror_c(errno) );
        i_read = 0;
    }

    return i_read;
}

/**
 * Parse MLST facts list
 *
 * Parses a MLST facts list (without the trailing space/filename)
 * pointed to by linep and fills the key/value variables.
 * If a not properly formatted fact is encountered, the
 * whole fact value will be in key and val will be NULL.
 *
 * \note This function modifies the linep pointer, so
 *       the original value for it must be saved to free
 *       it once done.
 * \retval false if at the end of the list
 * \retval true  if there are more list items to process
 */

static bool mlst_facts_iter(char **linep, const char **key, const char **val)
{
    // MLST format parsed here is 'key=val;key=val...;'
    // Lack of trailing ; at the end is accepted too, even though
    // not permitted by the standard.
    char *fact = strsep(linep, ";");
    *key = NULL;
    *val = NULL;

    if (fact == NULL || fact[0] == '\0')
        return false;

    // Separate key and value
    char *sep = strchr(fact, '=');
    if (sep) {
        *sep++ = '\0';
    }
    *key = fact;
    *val = sep;

    return true;
}

/**
 * Parse an RFC3659 Time value (Section 2.3)
 * and return the seconds since UNIX epoch.
 *
 * \retval -1       The timeval could not be parsed
 * \retval time_t   Time value in seconds since UNIX epoch
 */

static time_t ftp_mktime(const char *const timeval)
{
    #define RFC3659_TIMEVAL_MIN 14
    // A time-val needs at least 14 digits to represent:
    // - Year    (4)
    // - Month   (2)
    // - Day     (2)
    // - Hour    (2)
    // - Minutes (2)
    // - Seconds (2)

    char extra;
    int len;
    struct tm tm;
    // The trailing char is used to check that there is
    // no invalid trailing input after the parsed string
    // or make sure that a potential 15th character is a dot
    // for further parsing.
    int ret = sscanf(timeval, "%4d%2d%2d%2d%2d%2d%n%c",
                     &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
                     &tm.tm_hour, &tm.tm_min, &tm.tm_sec,
                     &len, &extra);

    // There are only two valid cases:
    // - A time-val without any fraction of seconds, in which case
    //   6 fields must be matched. There can be no trailing
    //   characters and the length is explicitly required to be 14.
    //   The last field is only present to make sure it does _not_ match-
    //   anything as no other trailing characters are supposed to be found
    //   after the time-val.
    // - A time-val with fractions of seconds in which the string must be
    //   strictly longer than 15 characters, all 7 fields must be set and the 7th
    //   field must be a dot. There must be at least one digit afterward but
    //   there is no other limitation to the number of digits after the dot.
    //   After the dot the only valid characters are digits and nothing else.
    if (len == RFC3659_TIMEVAL_MIN && (ret == 6 || (ret == 7 && extra == '.')))
    {
        
        // The optional fractions of seconds are of no interest here
        // as we can not represent them in time_t, but we still
        // need to parse them to make sure that the time-val is valid.
        if (ret == 7)
        {
            size_t i;
            for (i = RFC3659_TIMEVAL_MIN + 1; timeval[i] != '\0'; i++)
                if (!isdigit(timeval[i]))
                    return (time_t) -1;

            // We must have at least one digit
            if (i == RFC3659_TIMEVAL_MIN + 1)
                return (time_t) -1;
        }

        // Validate ranges according to RFC3659
        if (tm.tm_year < 1000 || tm.tm_year > 9999 ||
            tm.tm_mon  < 1    || tm.tm_mon  > 12   ||
            tm.tm_mday < 1    || tm.tm_mday > 31   ||
            tm.tm_hour < 0    || tm.tm_hour > 23   ||
            tm.tm_min  < 0    || tm.tm_min  > 59   ||
            tm.tm_sec  < 0    || tm.tm_sec  > 60)
            return (time_t) -1;

        // Year in tm starts from 1900
        tm.tm_year -= 1900;
        // Month in tm ranges from 0-11
        tm.tm_mon -= 1;
        // DST is unknown
        tm.tm_isdst = -1;

        return timegm(&tm);
    }

    return (time_t) -1;
}

/* Skip leading whitespace and the next whitespace-separated token. */
static char *SkipToken( char *p )
{
    while( *p == ' ' || *p == '\t' ) p++;
    while( *p && *p != ' ' && *p != '\t' ) p++;
    return p;
}

/**
 * Parse a Unix-style LIST entry (as emitted by `ls -l`)
 *
 * Handles the de-facto standard format used by vsftpd, proftpd,
 * pure-ftpd and similar servers when MLST is unavailable. Example:
 *   drwxr-xr-x  2 user group  4096 Apr 22 10:00 dirname
 *   -rw-r--r--  1 user group  1234 Apr 22 10:00 filename
 *   lrwxrwxrwx  1 user group    11 Apr 22 10:00 link -> target
*/

static int ParseListUnixEntry( char *line, int *type, long long *size,
                               char **filename )
{
    if( strlen( line ) < 11 )
        return -1;

    bool is_link = false;
    switch( line[0] )
    {
        case 'd': *type = ITEM_TYPE_DIRECTORY; break;
        case '-': *type = ITEM_TYPE_FILE;      break;
        case 'l': *type = ITEM_TYPE_UNKNOWN; is_link = true; break;
        default:
            return -1;
    }

    if( line[10] != ' ' && line[10] != '\t' )
        return -1;

    char *p = line + 10;

    p = SkipToken( p ); /* link count */
    p = SkipToken( p ); /* owner */
    p = SkipToken( p ); /* group */

    while( *p == ' ' || *p == '\t' ) p++;
    if( !isdigit( (unsigned char)*p ) || sscanf( p, "%lld", size ) != 1 )
        return -1;
    p = SkipToken( p );

    p = SkipToken( p ); /* month */
    p = SkipToken( p ); /* day */
    p = SkipToken( p ); /* time or year */

    while( *p == ' ' || *p == '\t' ) p++;
    if( !*p ) return -1;

    *filename = p;

    if( is_link )
    {
        char *arrow = NULL;
        for( char *q = strstr( p, " -> " ); q != NULL;
             q = strstr( q + 1, " -> " ) )
            arrow = q;
        if( arrow )
            *arrow = '\0';
    }

    return 0;
}

/**
 * Parse a DOS-style LIST entry (as emitted by Microsoft IIS in DOS mode)
 *
 * Example:
 *   04-22-26  10:00AM       <DIR>          dirname
 *   04-22-26  10:00AM                 1234 filename.ext
 *
 * The year may be 2 or 4 digits, and the AM/PM suffix is optional
 * (some servers emit 24-hour time).
 */

static int ParseListDosEntry( char *line, int *type, long long *size,
                              char **filename )
{
    unsigned mm, dd, yy, hh, mi;
    int n = 0;
    if( sscanf( line, "%2u-%2u-%4u %2u:%2u%n",
                &mm, &dd, &yy, &hh, &mi, &n ) != 5 )
        return -1;

    char *p = line + n;
    if( *p == 'A' || *p == 'P' )
    {
        if( p[1] != 'M' ) return -1;
        p += 2;
    }

    while( *p == ' ' || *p == '\t' ) p++;

    if( !strncmp( p, "<DIR>", 5 ) )
        *type = ITEM_TYPE_DIRECTORY;
    else if( isdigit( (unsigned char)*p ) && sscanf( p, "%lld", size ) == 1 )
        *type = ITEM_TYPE_FILE;
    else
        return -1;

    p = SkipToken( p );
    while( *p == ' ' || *p == '\t' ) p++;
    if( !*p ) return -1;

    *filename = p;
    return 0;
}

/*****************************************************************************
 * DirRead:
 *****************************************************************************/

static int DirRead (stream_t *p_access, input_item_node_t *p_current_node)
{
    access_sys_t *p_sys = p_access->p_sys;
    int i_ret = VLC_SUCCESS;

    assert( p_sys->data != NULL );
    assert( !p_sys->out );

    struct vlc_readdir_helper rdh;
    vlc_readdir_helper_init( &rdh, p_access, p_current_node );

    while (i_ret == VLC_SUCCESS)
    {
        char *psz_file;
        int type = ITEM_TYPE_UNKNOWN;
        long long size = 0;
        time_t mtime = (time_t)-1;

        char *psz_line = vlc_tls_GetLine( p_sys->data );
        if( psz_line == NULL )
            break;

        if( p_sys->features.b_mlst )
        {
            const char *key, *val;
            char *facts = psz_line;

            // Separate MLST and filename
            psz_file = strchr(psz_line, ' ');
            if (likely(psz_file)) {
                *psz_file++ = '\0';
            } else {
                msg_Warn( p_access, "No filename in MLST list found" );
                free( psz_line );
                continue;
            }

            while (mlst_facts_iter( &facts, &key, &val )) {
                if (val == NULL) {
                    msg_Warn( p_access, "Skipping invalid MLST fact '%s'", key);
                    continue;
                }
                if (!vlc_ascii_strcasecmp( key, "type" )) {
                    if (!vlc_ascii_strcasecmp( val, "dir" ))
                        type = ITEM_TYPE_DIRECTORY;
                    else if (!vlc_ascii_strcasecmp( val, "file" ))
                        type = ITEM_TYPE_FILE;
                } else if (!vlc_ascii_strcasecmp( key, "size" )) {
                    size = atoll(val);
                } else if (!vlc_ascii_strcasecmp( key, "modify" )) {
                    mtime = ftp_mktime(val);
                }
            }
        }
        else if( p_sys->features.b_list )
        {
            long long parsed_size = 0;
            if( ParseListUnixEntry( psz_line, &type, &parsed_size,
                                    &psz_file ) != 0 &&
                ParseListDosEntry( psz_line, &type, &parsed_size,
                                   &psz_file ) != 0 )
            {
                /* Unrecognized LIST format (or "total N" header): skip. */
                free( psz_line );
                continue;
            }
            size = parsed_size;
        }
        else
            psz_file = psz_line;

        /* Exclude this and parent directory */
        if (strcmp(psz_file, ".") == 0 || strcmp(psz_file, "..") == 0)
        {
            free(psz_line);
            continue;
        }

        char *psz_filename = vlc_uri_encode( psz_file );

        if (unlikely(psz_filename == NULL))
            continue;

        struct vlc_memstream ms;

        // Scheme
        vlc_memstream_open(&ms);
        vlc_memstream_puts(&ms, "ftp");
        if (p_sys->tlsmode != NONE)
        {
            if (p_sys->tlsmode != IMPLICIT)
                vlc_memstream_putc(&ms, 'e');
            vlc_memstream_putc(&ms, 's');
        }
        vlc_memstream_puts(&ms, "://");

        // Host
        // In case of IPv6, enclose in []
        if (strchr(p_sys->url.psz_host, ':') != NULL)
            vlc_memstream_printf(&ms, "[%s]", p_sys->url.psz_host);
        else
            vlc_memstream_puts(&ms, p_sys->url.psz_host);

        // Port
        // Only print if not the default for the scheme
        if (p_sys->url.i_port != ((p_sys->tlsmode != IMPLICIT) ? IPPORT_FTP
                                                               : IPPORT_FTPS))
            vlc_memstream_printf(&ms, ":%d", p_sys->url.i_port);

        // Separating / after host[:port]
        vlc_memstream_putc(&ms, '/');

        // Path to the current location, if any
        if (p_sys->url.psz_path != NULL)
            vlc_memstream_puts(&ms, p_sys->url.psz_path);

        // Filename
        vlc_memstream_puts(&ms, psz_filename);
        free(psz_filename);

        if (likely(vlc_memstream_close(&ms) == 0))
        {
            input_item_t *p_item;
            i_ret = vlc_readdir_helper_additem( &rdh, ms.ptr, NULL, psz_file,
                                                type, ITEM_NET, &p_item );

            if (i_ret == VLC_SUCCESS && p_item) {
                if (size > 0)
                    input_item_AddStat(p_item, "size", size);
                if (mtime != (time_t)-1)
                    input_item_AddStat(p_item, "mtime", mtime);
            }
            free(ms.ptr);
        }
        free( psz_line );
    }

    vlc_readdir_helper_finish( &rdh, i_ret == VLC_SUCCESS );
    return i_ret;
}

/*****************************************************************************
 * Write:
 *****************************************************************************/

#ifdef ENABLE_SOUT
static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
{
    access_sys_t *p_sys = GET_OUT_SYS(p_access);
    size_t i_write = 0;

    assert( p_sys->data != NULL );

    while( p_buffer != NULL )
    {
        block_t *p_next = p_buffer->p_next;

        i_write += vlc_tls_Write( p_sys->data,
                                  p_buffer->p_buffer, p_buffer->i_buffer );
        block_Release( p_buffer );

        p_buffer = p_next;
    }

    return i_write;
}
#endif

/*****************************************************************************
 * Control:
 *****************************************************************************/

static int Control( stream_t *p_access, int i_query, va_list args )
{
    access_sys_t *sys = p_access->p_sys;
    bool    *pb_bool;

    switch( i_query )
    {
        case STREAM_CAN_SEEK:
            pb_bool = va_arg( args, bool * );
            *pb_bool = true;
            break;
        case STREAM_CAN_FASTSEEK:
            pb_bool = va_arg( args, bool * );
            *pb_bool = false;
            break;
        case STREAM_CAN_PAUSE:
            pb_bool = va_arg( args, bool * );
            *pb_bool = true;    /* FIXME */
            break;
        case STREAM_CAN_CONTROL_PACE:
        case STREAM_CAN_DOWNLOAD:
            pb_bool = va_arg( args, bool * );
            *pb_bool = true;    /* FIXME */
            break;
        case STREAM_GET_SIZE:
            if( sys->size == UINT64_MAX )
                return VLC_EGENERIC;
            *va_arg( args, uint64_t * ) = sys->size;
            break;

        case STREAM_GET_PTS_DELAY:
            *va_arg( args, vlc_tick_t * ) =
                VLC_TICK_FROM_MS(var_InheritInteger( p_access, "network-caching" ));
            break;

        case STREAM_SET_PAUSE_STATE:
            if ( !va_arg( args, int ) )
                 return Seek( p_access, sys->offset );
            break;

        default:
            return VLC_EGENERIC;

    }
    return VLC_SUCCESS;
}

static int ftp_StartStream( vlc_object_t *p_access, access_sys_t *p_sys,
                            uint64_t i_start, bool b_directory )
{
    char psz_ipv4[16], *psz_ip = p_sys->sz_epsv_ip;
    int  i_answer;
    char *psz_arg, *psz_parser;
    int  i_port;

    assert( p_sys->data == NULL );

    if( ( ftp_SendCommand( p_access, p_sys, *psz_ip ? "EPSV" : "PASV" ) < 0 )
     || ( ftp_RecvCommand( p_access, p_sys, &i_answer, &psz_arg ) != 2 ) )
    {
        msg_Err( p_access, "cannot set passive mode" );
        return VLC_EGENERIC;
    }

    psz_parser = strchr( psz_arg, '(' );
    if( psz_parser == NULL )
    {
        free( psz_arg );
        msg_Err( p_access, "cannot parse passive mode response" );
        return VLC_EGENERIC;
    }

    if( *psz_ip )
    {
        if( sscanf( psz_parser, "(%*3c%u", &i_port ) < 1 )
        {
            free( psz_arg );
            msg_Err( p_access, "cannot parse passive mode response" );
            return VLC_EGENERIC;
        }
    }
    else
    {
        unsigned a1, a2, a3, a4, p1, p2;

        if( ( sscanf( psz_parser, "(%u,%u,%u,%u,%u,%u", &a1, &a2, &a3, &a4,
                      &p1, &p2 ) < 6 ) || ( a1 > 255 ) || ( a2 > 255 )
         || ( a3 > 255 ) || ( a4 > 255 ) || ( p1 > 255 ) || ( p2 > 255 ) )
        {
            free( psz_arg );
            msg_Err( p_access, "cannot parse passive mode response" );
            return VLC_EGENERIC;
        }

        snprintf( psz_ipv4, ARRAY_SIZE(psz_ipv4), "%u.%u.%u.%u", a1, a2, a3, a4 );
        psz_ip = psz_ipv4;
        i_port = (p1 << 8) | p2;
    }
    free( psz_arg );

    msg_Dbg( p_access, "ip:%s port:%d", psz_ip, i_port );

    if( ftp_SendCommand( p_access, p_sys, "TYPE I" ) < 0 ||
        ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) != 2 )
    {
        msg_Err( p_access, "cannot set binary transfer mode" );
        return VLC_EGENERIC;
    }

    if( i_start > 0 )
    {
        if( ftp_SendCommand( p_access, p_sys, "REST %"PRIu64, i_start ) < 0 ||
            ftp_RecvCommand( p_access, p_sys, &i_answer, NULL ) > 3 )
        {
            msg_Err( p_access, "cannot set restart offset" );
            return VLC_EGENERIC;
        }
    }

    msg_Dbg( p_access, "waiting for data connection..." );
    p_sys->data = vlc_tls_SocketOpenTCP( p_access, psz_ip, i_port );
    if( p_sys->data == NULL )
    {
        msg_Err( p_access, "failed to connect with server" );
        return VLC_EGENERIC;
    }
    msg_Dbg( p_access, "connection with \"%s:%d\" successful",
             psz_ip, i_port );

    if( b_directory )
    {
        if( p_sys->features.b_mlst &&
            ftp_SendCommand( p_access, p_sys, "MLSD" ) >= 0 &&
            ftp_RecvCommandInit( p_access, p_sys ) == 1 )
        {
            msg_Dbg( p_access, "Using MLST extension to list" );
        }
        else
        if( ftp_SendCommand( p_access, p_sys, "LIST" ) >= 0 &&
            ftp_RecvCommandInit( p_access, p_sys ) == 1 )
        {
            p_sys->features.b_mlst = false;
            p_sys->features.b_list = true;
            msg_Dbg( p_access, "Using LIST command to list directory" );
        }
        else
        if( ftp_SendCommand( p_access, p_sys, "NLST" ) < 0 ||
            ftp_RecvCommandInit( p_access, p_sys ) != 1 )
        {
            msg_Err( p_access, "cannot list directory contents" );
            return VLC_EGENERIC;
        }
        else
        {
            p_sys->features.b_mlst = false;
            p_sys->features.b_list = false;
            msg_Dbg( p_access, "Falling back to NLST to list directory" );
        }
    }
    else
    {
        /* "1xx" message */
        assert( p_sys->url.psz_path );
        if( ftp_SendCommand( p_access, p_sys, "%s %s",
                             p_sys->out ? "STOR" : "RETR",
                             p_sys->url.psz_path ) < 0
         || ftp_RecvCommandInit( p_access, p_sys ) != 1 )
        {
            msg_Err( p_access, "cannot retrieve file" );
            return VLC_EGENERIC;
        }
    }

    if( p_sys->tlsmode != NONE )
    {
        /* FIXME: Do Reuse TLS Session */
        /* TLS/SSL handshake */
        vlc_tls_t *secure = vlc_tls_ClientSessionCreate( p_sys->p_creds,
                            p_sys->data, p_sys->url.psz_host,
                            ( p_sys->tlsmode == EXPLICIT ) ? "ftpes-data"
                                                           : "ftps-data",
                                                         NULL, NULL );
        if( secure == NULL )
        {
            msg_Err( p_access, "cannot establish FTP/TLS session for data" \
                             ": server not allowing new session ?" );
            return VLC_EGENERIC;
        }
        p_sys->data = secure;
    }

    return VLC_SUCCESS;
}

static int ftp_StopStream ( vlc_object_t *p_access, access_sys_t *p_sys )
{
    int ret = VLC_SUCCESS;

    if( ftp_SendCommand( p_access, p_sys, "ABOR" ) < 0 )
    {
        msg_Warn( p_access, "cannot abort file" );
        ret = VLC_EGENERIC;
    }

    if( p_sys->data != NULL )
    {
        vlc_tls_Close( p_sys->data );
        p_sys->data = NULL;

        if( ret == VLC_SUCCESS )
            /* Read the final response from RETR/STOR, i.e. 426 or 226 */
            ftp_RecvCommand( p_access, p_sys, NULL, NULL );
    }

    if( ret == VLC_SUCCESS )
        /* Read the response from ABOR, i.e. 226 or 225 */
        ftp_RecvCommand( p_access, p_sys, NULL, NULL );

    return ret;
}

Messung
CHZG
84969892

¤ Dauer der Verarbeitung: 0.28 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.