tcp.c 16.3 KB
Newer Older
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
1 2 3 4
/*****************************************************************************
 * tcp.c:
 *****************************************************************************
 * Copyright (C) 2004-2005 the VideoLAN team
5
 * Copyright (C) 2005-2006 Rémi Denis-Courmont
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 * $Id$
 *
 * Authors: Laurent Aimar <fenrir@videolan.org>
 *          Rémi Denis-Courmont <rem # videolan.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
24 25 26 27 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
33 34 35
#include <vlc/vlc.h>

#include <errno.h>
36
#include <assert.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
37 38 39 40 41 42 43 44 45 46

#ifdef HAVE_FCNTL_H
#   include <fcntl.h>
#endif
#ifdef HAVE_SYS_TIME_H
#    include <sys/time.h>
#endif
#ifdef HAVE_UNISTD_H
#   include <unistd.h>
#endif
47 48 49
#ifdef HAVE_POLL
# include <poll.h>
#endif
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
50

Clément Stenac's avatar
Clément Stenac committed
51
#include <vlc_network.h>
52 53 54 55 56 57 58 59
#if defined (WIN32) || defined (UNDER_CE)
#   undef EINPROGRESS
#   define EINPROGRESS WSAEWOULDBLOCK
#   undef EINTR
#   define EINTR WSAEINTR
#   undef ETIMEDOUT
#   define ETIMEDOUT WSAETIMEDOUT
#endif
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
60 61

static int SocksNegociate( vlc_object_t *, int fd, int i_socks_version,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
62
                           const char *psz_user, const char *psz_passwd );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
63 64
static int SocksHandshakeTCP( vlc_object_t *,
                              int fd, int i_socks_version,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
65
                              const char *psz_user, const char *psz_passwd,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
66 67 68 69 70
                              const char *psz_host, int i_port );
extern int net_Socket( vlc_object_t *p_this, int i_family, int i_socktype,
                       int i_protocol );

/*****************************************************************************
71
 * __net_Connect:
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
72
 *****************************************************************************
73 74
 * Open a network connection.
 * @return socket handler or -1 on error.
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
75
 *****************************************************************************/
76 77
int __net_Connect( vlc_object_t *p_this, const char *psz_host, int i_port,
                   int type, int proto )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
78 79 80 81
{
    struct addrinfo hints, *res, *ptr;
    const char      *psz_realhost;
    char            *psz_socks;
82
    int             i_realport, i_val, i_handle = -1;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
83

84 85 86 87
    int evfd = vlc_object_waitpipe (p_this);
    if (evfd == -1)
        return -1;

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
88 89 90
    if( i_port == 0 )
        i_port = 80; /* historical VLC thing */

91

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
92 93 94
    memset( &hints, 0, sizeof( hints ) );
    hints.ai_socktype = SOCK_STREAM;

95 96
    psz_socks = var_CreateGetNonEmptyString( p_this, "socks" );
    if( psz_socks != NULL )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
97 98 99 100 101 102 103 104
    {
        char *psz = strchr( psz_socks, ':' );

        if( psz )
            *psz++ = '\0';

        psz_realhost = psz_socks;
        i_realport = ( psz != NULL ) ? atoi( psz ) : 1080;
105
        hints.ai_flags &= ~AI_NUMERICHOST;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
106

107 108 109
        msg_Dbg( p_this, "net: connecting to %s port %d (SOCKS) "
                 "for %s port %d", psz_realhost, i_realport,
                 psz_host, i_port );
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133

        /* We only implement TCP with SOCKS */
        switch( type )
        {
            case 0:
                type = SOCK_STREAM;
            case SOCK_STREAM:
                break;
            default:
                msg_Err( p_this, "Socket type not supported through SOCKS" );
                free( psz_socks );
                return -1;
        }
        switch( proto )
        {
            case 0:
                proto = IPPROTO_TCP;
            case IPPROTO_TCP:
                break;
            default:
                msg_Err( p_this, "Transport not supported through SOCKS" );
                free( psz_socks );
                return -1;
        }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
134 135 136 137 138 139 140 141 142 143 144
    }
    else
    {
        psz_realhost = psz_host;
        i_realport = i_port;

        msg_Dbg( p_this, "net: connecting to %s port %d", psz_realhost,
                 i_realport );
    }

    i_val = vlc_getaddrinfo( p_this, psz_realhost, i_realport, &hints, &res );
145 146
    free( psz_socks );

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
147 148 149 150 151 152 153
    if( i_val )
    {
        msg_Err( p_this, "cannot resolve %s port %d : %s", psz_realhost,
                 i_realport, vlc_gai_strerror( i_val ) );
        return -1;
    }

154
    for( ptr = res; ptr != NULL; ptr = ptr->ai_next )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
155
    {
156 157
        int fd = net_Socket( p_this, ptr->ai_family, type ?: ptr->ai_socktype,
                             proto ?: ptr->ai_protocol );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
158
        if( fd == -1 )
159
        {
160
            msg_Dbg( p_this, "socket error: %m" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
161
            continue;
162
        }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
163 164 165

        if( connect( fd, ptr->ai_addr, ptr->ai_addrlen ) )
        {
166
            int timeout, val;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
167

168
            if( net_errno != EINPROGRESS )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
169
            {
170
                msg_Err( p_this, "connection failed: %m" );
171
                goto next_ai;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
172
            }
173
            msg_Dbg( p_this, "connection: %m" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
174

175 176
            timeout = var_CreateGetInteger (p_this, "ipv4-timeout");
            if (timeout < 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
177 178
            {
                msg_Err( p_this, "invalid negative value for ipv4-timeout" );
179
                timeout = 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
180 181
            }

182 183 184 185 186 187 188 189 190 191 192
            struct pollfd ufd[2] = {
                { .fd = fd,   .events = POLLOUT },
                { .fd = evfd, .events = POLLIN },
            };

            do
                /* NOTE: timeout screwed up if we catch a signal (EINTR) */
                val = poll (ufd, sizeof (ufd) / sizeof (ufd[0]), timeout);
            while ((val == -1) && (net_errno == EINTR));

            switch (val)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
193
            {
194 195 196 197 198 199 200 201 202 203 204
                 case -1: /* error */
                     msg_Err (p_this, "connection polling error: %m");
                     goto next_ai;

                 case 0: /* timeout */
                     msg_Warn (p_this, "connection timed out");
                     goto next_ai;

                 default: /* something happended */
                     if (ufd[1].revents)
                         goto next_ai; /* LibVLC object killed */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
205 206
            }

207 208 209 210
            /* There is NO WAY around checking SO_ERROR.
             * Don't ifdef it out!!! */
            if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &val,
                            &(socklen_t){ sizeof (val) }) || val)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
211
            {
212 213
                errno = val;
                msg_Err (p_this, "connection failed: %m");
214
                goto next_ai;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
215 216
            }
        }
217

218
        msg_Dbg( p_this, "connection succeeded (socket = %d)", fd );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
219
        i_handle = fd; /* success! */
220 221 222 223 224
        break;

next_ai: /* failure */
        net_Close( fd );
        continue;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
225 226 227 228 229 230 231
    }

    vlc_freeaddrinfo( res );

    if( i_handle == -1 )
        return -1;

232
    if( psz_socks != NULL )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
233
    {
234
        /* NOTE: psz_socks already free'd! */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
235 236
        char *psz_user = var_CreateGetNonEmptyString( p_this, "socks-user" );
        char *psz_pwd  = var_CreateGetNonEmptyString( p_this, "socks-pwd" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
237 238 239 240

        if( SocksHandshakeTCP( p_this, i_handle, 5, psz_user, psz_pwd,
                               psz_host, i_port ) )
        {
241
            msg_Err( p_this, "SOCKS handshake failed" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
242 243 244 245 246 247 248 249 250 251 252 253
            net_Close( i_handle );
            i_handle = -1;
        }

        free( psz_user );
        free( psz_pwd );
    }

    return i_handle;
}


254 255 256 257 258 259 260 261 262 263 264 265
int net_AcceptSingle (vlc_object_t *obj, int lfd)
{
    int fd = accept (lfd, NULL, NULL);
    if (fd == -1)
    {
        if (net_errno != EAGAIN)
            msg_Err (obj, "accept failed (from socket %d): %m", lfd);
        return -1;
    }

    msg_Dbg (obj, "accepted socket %d (from socket %d)", fd, lfd);
    net_SetupSocket (fd);
266
    return fd;
267 268 269
}


Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
270 271 272 273 274
/*****************************************************************************
 * __net_Accept:
 *****************************************************************************
 * Accept a connection on a set of listening sockets and return it
 *****************************************************************************/
275
int __net_Accept( vlc_object_t *p_this, int *pi_fd, mtime_t i_wait )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
276
{
277
    int timeout = (i_wait < 0) ? -1 : i_wait / 1000;
278
    int evfd = vlc_object_waitpipe (p_this);
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
279

280 281
    if (evfd == -1)
        return -1;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
282

283
    assert( pi_fd != NULL );
284

285
    for (;;)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
286
    {
287 288 289
        unsigned n = 0;
        while (pi_fd[n] != -1)
            n++;
290
        struct pollfd ufd[n + 1];
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
291 292

        /* Initialize file descriptor set */
293
        for (unsigned i = 0; i <= n; i++)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
294
        {
295
            ufd[i].fd = (i < n) ? pi_fd[i] : evfd;
296 297
            ufd[i].events = POLLIN;
            ufd[i].revents = 0;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
298
        }
299 300
        if (evfd == -1)
            n--; /* avoid EBADF */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
301

302
        switch (poll (ufd, n, timeout))
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
303
        {
304 305
            case -1:
                if (net_errno != EINTR)
306
                    msg_Err (p_this, "poll error: %m");
307
            case 0:
308 309 310 311 312 313 314
                return -1; /* NOTE: p_this already unlocked */
        }

        if (ufd[n].revents)
        {
            errno = EINTR;
            break;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
315 316
        }

317
        for (unsigned i = 0; i < n; i++)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
318
        {
319
            if (ufd[i].revents == 0)
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
320 321
                continue;

322
            int sfd = ufd[i].fd;
323
            int fd = net_AcceptSingle (p_this, sfd);
324
            if (fd == -1)
325 326 327
                continue;

            /*
328 329
             * Move listening socket to the end to let the others in the
             * set a chance next time.
330
             */
331 332
            memmove (pi_fd + i, pi_fd + i + 1, n - (i + 1));
            pi_fd[n - 1] = sfd;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
333
            return fd;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
334 335 336 337 338 339 340 341 342 343 344 345 346
        }
    }
    return -1;
}


/*****************************************************************************
 * SocksNegociate:
 *****************************************************************************
 * Negociate authentication with a SOCKS server.
 *****************************************************************************/
static int SocksNegociate( vlc_object_t *p_obj,
                           int fd, int i_socks_version,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
347 348
                           const char *psz_socks_user,
                           const char *psz_socks_passwd )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
349 350 351 352 353 354 355 356 357 358
{
    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 */

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
359
    if( ( psz_socks_user == NULL ) && ( psz_socks_passwd == NULL ) )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436
        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,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
437
                              const char *psz_user, const char *psz_passwd,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
438 439 440 441 442 443 444 445 446 447
                              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;
    }

448
    if( i_socks_version == 5 &&
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
449
        SocksNegociate( p_obj, fd, i_socks_version,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
450
                        psz_user, psz_passwd ) )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
451 452 453 454
        return VLC_EGENERIC;

    if( i_socks_version == 4 )
    {
455
        struct addrinfo hints, *p_res;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
456 457

        /* v4 only support ipv4 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
458
        memset (&hints, 0, sizeof (hints));
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
        hints.ai_family = AF_INET;
        if( vlc_getaddrinfo( p_obj, psz_host, 0, &hints, &p_res ) )
            return VLC_EGENERIC;

        buffer[0] = i_socks_version;
        buffer[1] = 0x01;               /* CONNECT */
        SetWBE( &buffer[2], i_port );   /* Port */
        memcpy( &buffer[4],             /* Address */
                &((struct sockaddr_in *)(p_res->ai_addr))->sin_addr, 4 );
        vlc_freeaddrinfo( p_res );

        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;
523
        else
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
            return VLC_EGENERIC;

        if( net_Read( p_obj, fd, NULL, buffer, i_len, VLC_TRUE ) != i_len )
            return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

void net_ListenClose( int *pi_fd )
{
    if( pi_fd != NULL )
    {
        int *pi;

        for( pi = pi_fd; *pi != -1; pi++ )
            net_Close( *pi );
        free( pi_fd );
    }
}