]> git.sesse.net Git - vlc/blobdiff - src/misc/net.c
Fix invalid abusive pointer usage (heck, there was a warning!)
[vlc] / src / misc / net.c
index ebe98d4a5fa227949ae86518a33b3f8241406189..40a276061009b8b5bba9a950759b57332fafbbf2 100644 (file)
@@ -27,9 +27,7 @@
 #include <stdlib.h>
 #include <vlc/vlc.h>
 
-#ifdef HAVE_ERRNO_H
-#   include <errno.h>
-#endif
+#include <errno.h>
 
 #ifdef HAVE_FCNTL_H
 #   include <fcntl.h>
 #   endif
 #else
 #   include <sys/socket.h>
+#   include <netinet/in.h>
+#   ifdef HAVE_ARPA_INET_H
+#       include <arpa/inet.h>
+#   endif
+#   include <netdb.h>
 #endif
 
 #ifdef HAVE_UNISTD_H
 
 #include "network.h"
 
+#ifndef INADDR_ANY
+#   define INADDR_ANY  0x00000000
+#endif
+#ifndef INADDR_NONE
+#   define INADDR_NONE 0xFFFFFFFF
+#endif
+
+static int SocksNegociate( vlc_object_t *, int fd, int i_socks_version,
+                           char *psz_socks_user, char *psz_socks_passwd );
+static int SocksHandshakeTCP( vlc_object_t *,
+                              int fd, int i_socks_version,
+                              char *psz_socks_user, char *psz_socks_passwd,
+                              const char *psz_host, int i_port );
+
+/*****************************************************************************
+ * net_ConvertIPv4:
+ *****************************************************************************
+ * Open a TCP connection and return a handle
+ *****************************************************************************/
+int net_ConvertIPv4( uint32_t *p_addr, const char * psz_address )
+{
+    /* Reset struct */
+    if( !*psz_address )
+    {
+        *p_addr = INADDR_ANY;
+    }
+    else
+    {
+        struct hostent *p_hostent;
+
+        /* Try to convert address directly from in_addr - this will work if
+         * psz_address is dotted decimal. */
+#ifdef HAVE_ARPA_INET_H
+        if( !inet_aton( psz_address, (struct in_addr *)p_addr ) )
+#else
+        *p_addr = inet_addr( psz_address );
+        if( *p_addr == INADDR_NONE )
+#endif
+        {
+            /* We have a fqdn, try to find its address */
+            if ( (p_hostent = gethostbyname( psz_address )) == NULL )
+            {
+                return VLC_EGENERIC;
+            }
+
+            /* Copy the first address of the host in the socket address */
+            memcpy( p_addr, p_hostent->h_addr_list[0],
+                    p_hostent->h_length );
+        }
+    }
+    return VLC_SUCCESS;
+}
 
 /*****************************************************************************
  * __net_OpenTCP:
  *****************************************************************************
  * Open a TCP connection and return a handle
  *****************************************************************************/
-int __net_OpenTCP( vlc_object_t *p_this, char *psz_host, int i_port )
+int __net_OpenTCP( vlc_object_t *p_this, const char *psz_host, int i_port )
 {
     vlc_value_t      val;
     void            *private;
@@ -93,11 +148,32 @@ int __net_OpenTCP( vlc_object_t *p_this, char *psz_host, int i_port )
     sock.i_type = NETWORK_TCP;
     sock.psz_bind_addr   = "";
     sock.i_bind_port     = 0;
-    sock.psz_server_addr = psz_host;
-    sock.i_server_port   = i_port;
     sock.i_ttl           = 0;
 
-    msg_Dbg( p_this, "net: connecting to '%s:%d'", psz_host, i_port );
+    var_Create( p_this, "socks", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
+    var_Get( p_this, "socks", &val );
+    if( *val.psz_string && *val.psz_string != ':' )
+    {
+        char *psz = strchr( val.psz_string, ':' );
+
+        if( psz )
+            *psz++ = '\0';
+
+        sock.psz_server_addr = (char*)val.psz_string;
+        sock.i_server_port   = psz ? atoi( psz ) : 1080;
+
+        msg_Dbg( p_this, "net: connecting to '%s:%d' for '%s:%d'",
+                 sock.psz_server_addr, sock.i_server_port,
+                 psz_host, i_port );
+    }
+    else
+    {
+        sock.psz_server_addr = (char*)psz_host;
+        sock.i_server_port   = i_port;
+        msg_Dbg( p_this, "net: connecting to '%s:%d'", psz_host, i_port );
+    }
+
+
     private = p_this->p_private;
     p_this->p_private = (void*)&sock;
     if( !( p_network = module_Need( p_this, "network", psz_network, 0 ) ) )
@@ -109,6 +185,25 @@ int __net_OpenTCP( vlc_object_t *p_this, char *psz_host, int i_port )
     module_Unneed( p_this, p_network );
     p_this->p_private = private;
 
+    if( *val.psz_string && *val.psz_string != ':' )
+    {
+        char *psz_user = var_CreateGetString( p_this, "socks-user" );
+        char *psz_pwd  = var_CreateGetString( p_this, "socks-pwd" );
+
+        if( SocksHandshakeTCP( p_this, sock.i_handle, 5,
+                               psz_user, psz_pwd,
+                               psz_host, i_port ) )
+        {
+            msg_Err( p_this, "failed to use the SOCKS server" );
+            net_Close( sock.i_handle );
+            return -1;
+        }
+
+        free( psz_user );
+        free( psz_pwd );
+    }
+    free( val.psz_string );
+
     return sock.i_handle;
 }
 
@@ -195,7 +290,7 @@ int __net_Accept( vlc_object_t *p_this, int fd, mtime_t i_wait )
         }
         else if( i_ret < 0 )
         {
-#ifdef WIN32
+#if defined(WIN32) || defined(UNDER_CE)
             msg_Err( p_this, "network select error (%i)", WSAGetLastError() );
 #else
             msg_Err( p_this, "network select error (%s)", strerror(errno) );
@@ -205,7 +300,7 @@ int __net_Accept( vlc_object_t *p_this, int fd, mtime_t i_wait )
 
         if( ( i_ret = accept( fd, 0, 0 ) ) <= 0 )
         {
-#ifdef WIN32
+#if defined(WIN32) || defined(UNDER_CE)
             msg_Err( p_this, "accept failed (%i)", WSAGetLastError() );
 #else
             msg_Err( p_this, "accept failed (%s)", strerror(errno) );
@@ -299,8 +394,8 @@ void net_Close( int fd )
  * If b_rety is true, then we repeat until we have read the right amount of
  * data
  *****************************************************************************/
-int __net_Read( vlc_object_t *p_this, int fd, uint8_t *p_data, int i_data,
-                vlc_bool_t b_retry )
+int __net_Read( vlc_object_t *p_this, int fd, v_socket_t *p_vs,
+                uint8_t *p_data, int i_data, vlc_bool_t b_retry )
 {
     struct timeval  timeout;
     fd_set          fds_r, fds_e;
@@ -333,13 +428,19 @@ int __net_Read( vlc_object_t *p_this, int fd, uint8_t *p_data, int i_data,
 
         if( i_ret < 0 )
         {
+#if defined(WIN32) || defined(UNDER_CE)
+            msg_Err( p_this, "network select error" );
+#else
             msg_Err( p_this, "network select error (%s)", strerror(errno) );
+#endif
             return i_total > 0 ? i_total : -1;
         }
 
-        if( ( i_recv = recv( fd, p_data, i_data, 0 ) ) < 0 )
+        if( ( i_recv = (p_vs != NULL)
+              ? p_vs->pf_recv( p_vs->p_sys, p_data, i_data )
+              : recv( fd, p_data, i_data, 0 ) ) < 0 )
         {
-#ifdef WIN32
+#if defined(WIN32) || defined(UNDER_CE)
             /* For udp only */
             /* On win32 recv() will fail if the datagram doesn't fit inside
              * the passed buffer, even though the buffer will be filled with
@@ -379,8 +480,8 @@ int __net_Read( vlc_object_t *p_this, int fd, uint8_t *p_data, int i_data,
  *****************************************************************************
  * Read from a network socket, non blocking mode (with timeout)
  *****************************************************************************/
-int __net_ReadNonBlock( vlc_object_t *p_this, int fd, uint8_t *p_data,
-                        int i_data, mtime_t i_wait)
+int __net_ReadNonBlock( vlc_object_t *p_this, int fd, v_socket_t *p_vs,
+                        uint8_t *p_data, int i_data, mtime_t i_wait)
 {
     struct timeval  timeout;
     fd_set          fds_r, fds_e;
@@ -404,7 +505,11 @@ int __net_ReadNonBlock( vlc_object_t *p_this, int fd, uint8_t *p_data,
     }
     else if( i_ret < 0 )
     {
+#if defined(WIN32) || defined(UNDER_CE)
+        msg_Err( p_this, "network select error" );
+#else
         msg_Err( p_this, "network select error (%s)", strerror(errno) );
+#endif
         return -1;
     }
     else if( i_ret == 0)
@@ -413,10 +518,14 @@ int __net_ReadNonBlock( vlc_object_t *p_this, int fd, uint8_t *p_data,
     }
     else
     {
-        if( fd == 0 /*STDIN_FILENO*/ ) i_recv = read( fd, p_data, i_data ); else
-        if( ( i_recv = recv( fd, p_data, i_data, 0 ) ) <= 0 )
+#if !defined(UNDER_CE)
+        if( fd == 0/*STDIN_FILENO*/ ) i_recv = read( fd, p_data, i_data ); else
+#endif
+        if( ( i_recv = (p_vs != NULL)
+              ? p_vs->pf_recv( p_vs->p_sys, p_data, i_data )
+              : recv( fd, p_data, i_data, 0 ) ) <= 0 )
         {
-#ifdef WIN32
+#if defined(WIN32) || defined(UNDER_CE)
             /* For udp only */
             /* On win32 recv() will fail if the datagram doesn't fit inside
              * the passed buffer, even though the buffer will be filled with
@@ -441,8 +550,94 @@ int __net_ReadNonBlock( vlc_object_t *p_this, int fd, uint8_t *p_data,
     return -1;
 }
 
+/*****************************************************************************
+ * __net_Select:
+ *****************************************************************************
+ * Read from several sockets (with timeout). Takes data from the first socket
+ * that has some.
+ *****************************************************************************/
+int __net_Select( vlc_object_t *p_this, int *pi_fd, v_socket_t **pp_vs,
+                  int i_fd, uint8_t *p_data, int i_data, mtime_t i_wait )
+{
+    struct timeval  timeout;
+    fd_set          fds_r, fds_e;
+    int             i_recv;
+    int             i_ret;
+    int             i;
+    int             i_max_fd = 0;
+
+    /* Initialize file descriptor set */
+    FD_ZERO( &fds_r );
+    FD_ZERO( &fds_e );
+
+    for( i = 0 ; i < i_fd ; i++)
+    {
+        if( pi_fd[i] > i_max_fd ) i_max_fd = pi_fd[i];
+        FD_SET( pi_fd[i], &fds_r );
+        FD_SET( pi_fd[i], &fds_e );
+    }
+
+    timeout.tv_sec = 0;
+    timeout.tv_usec = i_wait;
+
+    i_ret = select( i_max_fd + 1, &fds_r, NULL, &fds_e, &timeout );
+
+    if( i_ret < 0 && errno == EINTR )
+    {
+        return 0;
+    }
+    else if( i_ret < 0 )
+    {
+        msg_Err( p_this, "network select error (%s)", strerror(errno) );
+        return -1;
+    }
+    else if( i_ret == 0 )
+    {
+        return 0;
+    }
+    else
+    {
+        for( i = 0 ; i < i_fd ; i++)
+        {
+            if( FD_ISSET( pi_fd[i], &fds_r ) )
+            {
+                i_recv = ((pp_vs != NULL) && (pp_vs[i] != NULL))
+                         ? pp_vs[i]->pf_recv( pp_vs[i]->p_sys, p_data, i_data )
+                         : recv( pi_fd[i], p_data, i_data, 0 );
+                if( i_recv <= 0 )
+                {
+#ifdef WIN32
+                    /* For udp only */
+                    /* On win32 recv() will fail if the datagram doesn't
+                     * fit inside the passed buffer, even though the buffer
+                     *  will be filled with the first part of the datagram. */
+                    if( WSAGetLastError() == WSAEMSGSIZE )
+                    {
+                        msg_Err( p_this, "recv() failed. "
+                             "Increase the mtu size (--mtu option)" );
+                    }
+                    else
+                        msg_Err( p_this, "recv failed (%i)",
+                                        WSAGetLastError() );
+#else
+                     msg_Err( p_this, "recv failed (%s)", strerror(errno) );
+#endif
+                    return VLC_EGENERIC;
+                }
+
+                return i_recv;
+            }
+        }
+    }
+
+    /* We will never be here */
+    return -1;
+}
+
+
 /* Write exact amount requested */
-int __net_Write( vlc_object_t *p_this, int fd, uint8_t *p_data, int i_data )
+int __net_Write( vlc_object_t *p_this, int fd, v_socket_t *p_vs,
+                 uint8_t *p_data, int i_data )
 {
     struct timeval  timeout;
     fd_set          fds_w, fds_e;
@@ -476,11 +671,17 @@ int __net_Write( vlc_object_t *p_this, int fd, uint8_t *p_data, int i_data )
 
         if( i_ret < 0 )
         {
+#if defined(WIN32) || defined(UNDER_CE)
+            msg_Err( p_this, "network select error" );
+#else
             msg_Err( p_this, "network select error (%s)", strerror(errno) );
+#endif
             return i_total > 0 ? i_total : -1;
         }
 
-        if( ( i_send = send( fd, p_data, i_data, 0 ) ) < 0 )
+        if( ( i_send = (p_vs != NULL)
+                       ? p_vs->pf_send( p_vs->p_sys, p_data, i_data )
+                       : send( fd, p_data, i_data, 0 ) ) < 0 )
         {
             /* XXX With udp for example, it will issue a message if the host
              * isn't listening */
@@ -495,7 +696,7 @@ int __net_Write( vlc_object_t *p_this, int fd, uint8_t *p_data, int i_data )
     return i_total;
 }
 
-char *__net_Gets( vlc_object_t *p_this, int fd )
+char *__net_Gets( vlc_object_t *p_this, int fd, v_socket_t *p_vs )
 {
     char *psz_line = malloc( 1024 );
     int  i_line = 0;
@@ -504,7 +705,7 @@ char *__net_Gets( vlc_object_t *p_this, int fd )
 
     for( ;; )
     {
-        if( net_Read( p_this, fd, &psz_line[i_line], 1, VLC_TRUE ) != 1 )
+        if( net_Read( p_this, fd, p_vs, &psz_line[i_line], 1, VLC_TRUE ) != 1 )
         {
             psz_line[i_line] = '\0';
             break;
@@ -539,27 +740,221 @@ char *__net_Gets( vlc_object_t *p_this, int fd )
     return psz_line;
 }
 
-int net_Printf( vlc_object_t *p_this, int fd, const char *psz_fmt, ... )
+int net_Printf( vlc_object_t *p_this, int fd, v_socket_t *p_vs,
+                const char *psz_fmt, ... )
 {
     int i_ret;
     va_list args;
     va_start( args, psz_fmt );
-    i_ret = net_vaPrintf( p_this, fd, psz_fmt, args );
+    i_ret = net_vaPrintf( p_this, fd, p_vs, psz_fmt, args );
     va_end( args );
 
     return i_ret;
 }
 
-int __net_vaPrintf( vlc_object_t *p_this, int fd, const char *psz_fmt,
-                    va_list args )
+int __net_vaPrintf( vlc_object_t *p_this, int fd, v_socket_t *p_vs,
+                    const char *psz_fmt, va_list args )
 {
     char    *psz;
     int     i_size, i_ret;
 
     vasprintf( &psz, psz_fmt, args );
     i_size = strlen( psz );
-    i_ret = __net_Write( p_this, fd, psz, i_size ) < i_size ? -1 : i_size;
+    i_ret = __net_Write( p_this, fd, p_vs, psz, i_size ) < i_size ? -1 : i_size;
     free( psz );
 
     return i_ret;
 }
+
+
+
+/*****************************************************************************
+ * SocksNegociate:
+ *****************************************************************************
+ * Negociate authentication with a SOCKS server.
+ *****************************************************************************/
+static int SocksNegociate( vlc_object_t *p_obj,
+                           int fd, int i_socks_version,
+                           char *psz_socks_user,
+                           char *psz_socks_passwd )
+{
+    uint8_t buffer[128+2*256];
+    int i_len;
+    vlc_bool_t b_auth = VLC_FALSE;
+
+    if( i_socks_version != 5 )
+        return VLC_SUCCESS;
+
+    /* We negociate authentication */
+
+    if( psz_socks_user && psz_socks_passwd &&
+        *psz_socks_user && *psz_socks_passwd )
+        b_auth = VLC_TRUE;
+
+    buffer[0] = i_socks_version;    /* SOCKS version */
+    if( b_auth )
+    {
+        buffer[1] = 2;                  /* Number of methods */
+        buffer[2] = 0x00;               /* - No auth required */
+        buffer[3] = 0x02;               /* - USer/Password */
+        i_len = 4;
+    }
+    else
+    {
+        buffer[1] = 1;                  /* Number of methods */
+        buffer[2] = 0x00;               /* - No auth required */
+        i_len = 3;
+    }
+    
+    if( net_Write( p_obj, fd, NULL, buffer, i_len ) != i_len )
+        return VLC_EGENERIC;
+    if( net_Read( p_obj, fd, NULL, buffer, 2, VLC_TRUE ) != 2 )
+        return VLC_EGENERIC;
+
+    msg_Dbg( p_obj, "socks: v=%d method=%x", buffer[0], buffer[1] );
+
+    if( buffer[1] == 0x00 )
+    {
+        msg_Dbg( p_obj, "socks: no authentication required" );
+    }
+    else if( buffer[1] == 0x02 )
+    {
+        int i_len1 = __MIN( strlen(psz_socks_user), 255 );
+        int i_len2 = __MIN( strlen(psz_socks_passwd), 255 );
+        msg_Dbg( p_obj, "socks: username/password authentication" );
+
+        /* XXX: we don't support user/pwd > 255 (truncated)*/
+        buffer[0] = i_socks_version;        /* Version */
+        buffer[1] = i_len1;                 /* User length */
+        memcpy( &buffer[2], psz_socks_user, i_len1 );
+        buffer[2+i_len1] = i_len2;          /* Password length */
+        memcpy( &buffer[2+i_len1+1], psz_socks_passwd, i_len2 );
+
+        i_len = 3 + i_len1 + i_len2;
+
+        if( net_Write( p_obj, fd, NULL, buffer, i_len ) != i_len )
+            return VLC_EGENERIC;
+
+        if( net_Read( p_obj, fd, NULL, buffer, 2, VLC_TRUE ) != 2 )
+            return VLC_EGENERIC;
+
+        msg_Dbg( p_obj, "socks: v=%d status=%x", buffer[0], buffer[1] );
+        if( buffer[1] != 0x00 )
+        {
+            msg_Err( p_obj, "socks: authentication rejected" );
+            return VLC_EGENERIC;
+        }
+    }
+    else
+    {
+        if( b_auth )
+            msg_Err( p_obj, "socks: unsupported authentication method %x",
+                     buffer[0] );
+        else
+            msg_Err( p_obj, "socks: authentification needed" );
+        return VLC_EGENERIC;
+    }
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * SocksHandshakeTCP:
+ *****************************************************************************
+ * Open a TCP connection using a SOCKS server and return a handle (RFC 1928)
+ *****************************************************************************/
+static int SocksHandshakeTCP( vlc_object_t *p_obj,
+                              int fd,
+                              int i_socks_version,
+                              char *psz_socks_user, char *psz_socks_passwd,
+                              const char *psz_host, int i_port )
+{
+    uint8_t buffer[128+2*256];
+
+    if( i_socks_version != 4 && i_socks_version != 5 )
+    {
+        msg_Warn( p_obj, "invalid socks protocol version %d", i_socks_version );
+        i_socks_version = 5;
+    }
+
+    if( i_socks_version == 5 && 
+        SocksNegociate( p_obj, fd, i_socks_version,
+                        psz_socks_user, psz_socks_passwd ) )
+        return VLC_EGENERIC;
+
+    if( i_socks_version == 4 )
+    {
+        uint32_t addr;
+
+        /* v4 only support ipv4 */
+        if( net_ConvertIPv4( &addr, psz_host ) )
+            return VLC_EGENERIC;
+
+        buffer[0] = i_socks_version;
+        buffer[1] = 0x01;               /* CONNECT */
+        SetWBE( &buffer[2], i_port );   /* Port */
+        memcpy( &buffer[4], &addr, 4 ); /* Addresse */
+        buffer[8] = 0;                  /* Empty user id */
+
+        if( net_Write( p_obj, fd, NULL, buffer, 9 ) != 9 )
+            return VLC_EGENERIC;
+        if( net_Read( p_obj, fd, NULL, buffer, 8, VLC_TRUE ) != 8 )
+            return VLC_EGENERIC;
+
+        msg_Dbg( p_obj, "socks: v=%d cd=%d",
+                 buffer[0], buffer[1] );
+
+        if( buffer[1] != 90 )
+            return VLC_EGENERIC;
+    }
+    else if( i_socks_version == 5 )
+    {
+        int i_hlen = __MIN(strlen( psz_host ), 255);
+        int i_len;
+
+        buffer[0] = i_socks_version;    /* Version */
+        buffer[1] = 0x01;               /* Cmd: connect */
+        buffer[2] = 0x00;               /* Reserved */
+        buffer[3] = 3;                  /* ATYP: for now domainname */
+
+        buffer[4] = i_hlen;
+        memcpy( &buffer[5], psz_host, i_hlen );
+        SetWBE( &buffer[5+i_hlen], i_port );
+
+        i_len = 5 + i_hlen + 2;
+
+
+        if( net_Write( p_obj, fd, NULL, buffer, i_len ) != i_len )
+            return VLC_EGENERIC;
+
+        /* Read the header */
+        if( net_Read( p_obj, fd, NULL, buffer, 5, VLC_TRUE ) != 5 )
+            return VLC_EGENERIC;
+
+        msg_Dbg( p_obj, "socks: v=%d rep=%d atyp=%d",
+                 buffer[0], buffer[1], buffer[3] );
+
+        if( buffer[1] != 0x00 )
+        {
+            msg_Err( p_obj, "socks: CONNECT request failed\n" );
+            return VLC_EGENERIC;
+        }
+
+        /* Read the remaining bytes */
+        if( buffer[3] == 0x01 )
+            i_len = 4-1 + 2;
+        else if( buffer[3] == 0x03 )
+            i_len = buffer[4] + 2;
+        else if( buffer[3] == 0x04 )
+            i_len = 16-1+2;
+        else 
+            return VLC_EGENERIC;
+
+        if( net_Read( p_obj, fd, NULL, buffer, i_len, VLC_TRUE ) != i_len )
+            return VLC_EGENERIC;
+    }
+
+    return VLC_SUCCESS;
+}
+
+