]> git.sesse.net Git - vlc/blob - src/network/tcp.c
Sort out io.c
[vlc] / src / network / tcp.c
1 /*****************************************************************************
2  * tcp.c:
3  *****************************************************************************
4  * Copyright (C) 2004-2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@videolan.org>
8  *          RĂ©mi Denis-Courmont <rem # videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>
29 #include <vlc/vlc.h>
30
31 #include <errno.h>
32
33 #ifdef HAVE_FCNTL_H
34 #   include <fcntl.h>
35 #endif
36 #ifdef HAVE_SYS_TIME_H
37 #    include <sys/time.h>
38 #endif
39 #ifdef HAVE_UNISTD_H
40 #   include <unistd.h>
41 #endif
42
43 #include "network.h"
44
45 static int SocksNegociate( vlc_object_t *, int fd, int i_socks_version,
46                            char *psz_socks_user, char *psz_socks_passwd );
47 static int SocksHandshakeTCP( vlc_object_t *,
48                               int fd, int i_socks_version,
49                               char *psz_socks_user, char *psz_socks_passwd,
50                               const char *psz_host, int i_port );
51 extern int net_Socket( vlc_object_t *p_this, int i_family, int i_socktype,
52                        int i_protocol );
53
54 /*****************************************************************************
55  * __net_ConnectTCP:
56  *****************************************************************************
57  * Open a TCP connection and return a handle
58  *****************************************************************************/
59 int __net_ConnectTCP( vlc_object_t *p_this, const char *psz_host, int i_port )
60 {
61     struct addrinfo hints, *res, *ptr;
62     const char      *psz_realhost;
63     char            *psz_socks;
64     int             i_realport, i_val, i_handle = -1;
65     vlc_bool_t      b_unreach = VLC_FALSE;
66
67     if( i_port == 0 )
68         i_port = 80; /* historical VLC thing */
69
70     memset( &hints, 0, sizeof( hints ) );
71     hints.ai_socktype = SOCK_STREAM;
72
73     psz_socks = var_CreateGetString( p_this, "socks" );
74     if( *psz_socks && *psz_socks != ':' )
75     {
76         char *psz = strchr( psz_socks, ':' );
77
78         if( psz )
79             *psz++ = '\0';
80
81         psz_realhost = psz_socks;
82         i_realport = ( psz != NULL ) ? atoi( psz ) : 1080;
83
84         msg_Dbg( p_this, "net: connecting to %s port %d for %s port %d",
85                  psz_realhost, i_realport, psz_host, i_port );
86     }
87     else
88     {
89         psz_realhost = psz_host;
90         i_realport = i_port;
91
92         msg_Dbg( p_this, "net: connecting to %s port %d", psz_realhost,
93                  i_realport );
94     }
95
96     i_val = vlc_getaddrinfo( p_this, psz_realhost, i_realport, &hints, &res );
97     if( i_val )
98     {
99         msg_Err( p_this, "cannot resolve %s port %d : %s", psz_realhost,
100                  i_realport, vlc_gai_strerror( i_val ) );
101         free( psz_socks );
102         return -1;
103     }
104
105     for( ptr = res; (ptr != NULL) && (i_handle == -1); ptr = ptr->ai_next )
106     {
107         int fd;
108
109         fd = net_Socket( p_this, ptr->ai_family, ptr->ai_socktype,
110                          ptr->ai_protocol );
111         if( fd == -1 )
112             continue;
113
114         if( connect( fd, ptr->ai_addr, ptr->ai_addrlen ) )
115         {
116             socklen_t i_val_size = sizeof( i_val );
117             div_t d;
118             struct timeval tv;
119             vlc_value_t timeout;
120
121 #if defined( WIN32 ) || defined( UNDER_CE )
122             if( WSAGetLastError() != WSAEWOULDBLOCK )
123             {
124                 if( WSAGetLastError () == WSAENETUNREACH )
125                     b_unreach = VLC_TRUE;
126                 else
127                     msg_Warn( p_this, "connection to %s port %d failed (%d)",
128                               psz_host, i_port, WSAGetLastError( ) );
129                 net_Close( fd );
130                 continue;
131             }
132 #else
133             if( errno != EINPROGRESS )
134             {
135                 if( errno == ENETUNREACH )
136                     b_unreach = VLC_TRUE;
137                 else
138                     msg_Warn( p_this, "connection to %s port %d : %s", psz_host,
139                               i_port, strerror( errno ) );
140                 net_Close( fd );
141                 continue;
142             }
143 #endif
144
145             var_Create( p_this, "ipv4-timeout",
146                         VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
147             var_Get( p_this, "ipv4-timeout", &timeout );
148             if( timeout.i_int < 0 )
149             {
150                 msg_Err( p_this, "invalid negative value for ipv4-timeout" );
151                 timeout.i_int = 0;
152             }
153             d = div( timeout.i_int, 100 );
154
155             msg_Dbg( p_this, "connection in progress" );
156             do
157             {
158                 fd_set fds;
159
160                 if( p_this->b_die )
161                 {
162                     msg_Dbg( p_this, "connection aborted" );
163                     net_Close( fd );
164                     vlc_freeaddrinfo( res );
165                     free( psz_socks );
166                     return -1;
167                 }
168
169                 /* Initialize file descriptor set */
170                 FD_ZERO( &fds );
171                 FD_SET( fd, &fds );
172
173                 /* We'll wait 0.1 second if nothing happens */
174                 tv.tv_sec = 0;
175                 tv.tv_usec = (d.quot > 0) ? 100000 : (1000 * d.rem);
176
177                 i_val = select( fd + 1, NULL, &fds, NULL, &tv );
178
179                 if( d.quot <= 0 )
180                 {
181                     msg_Dbg( p_this, "connection timed out" );
182                     net_Close( fd );
183                     fd = -1;
184                     break;
185                 }
186
187                 d.quot--;
188             }
189             while( ( i_val == 0 ) || ( ( i_val < 0 ) &&
190 #if defined( WIN32 ) || defined( UNDER_CE )
191                             ( WSAGetLastError() == WSAEWOULDBLOCK )
192 #else
193                             ( errno == EINTR )
194 #endif
195                      ) );
196
197             if( fd == -1 )
198                 continue; /* timeout */
199
200             if( i_val < 0 )
201             {
202                 msg_Warn( p_this, "connection aborted (select failed)" );
203                 net_Close( fd );
204                 continue;
205             }
206
207 #if !defined( SYS_BEOS ) && !defined( UNDER_CE )
208             if( getsockopt( fd, SOL_SOCKET, SO_ERROR, (void*)&i_val,
209                             &i_val_size ) == -1 || i_val != 0 )
210             {
211                 if( i_val == ENETUNREACH )
212                     b_unreach = VLC_TRUE;
213                 else
214                 {
215 #ifdef WIN32
216                     msg_Warn( p_this, "connection to %s port %d failed (%d)",
217                               psz_host, i_port, WSAGetLastError( ) );
218 #else
219                     msg_Warn( p_this, "connection to %s port %d : %s", psz_host,
220                               i_port, strerror( i_val ) );
221 #endif
222                 }
223                 net_Close( fd );
224                 continue;
225             }
226 #endif
227         }
228         i_handle = fd; /* success! */
229     }
230
231     vlc_freeaddrinfo( res );
232
233     if( i_handle == -1 )
234     {
235         if( b_unreach )
236             msg_Err( p_this, "Host %s port %d is unreachable", psz_host,
237                      i_port );
238         return -1;
239     }
240
241     if( *psz_socks && *psz_socks != ':' )
242     {
243         char *psz_user = var_CreateGetString( p_this, "socks-user" );
244         char *psz_pwd  = var_CreateGetString( p_this, "socks-pwd" );
245
246         if( SocksHandshakeTCP( p_this, i_handle, 5, psz_user, psz_pwd,
247                                psz_host, i_port ) )
248         {
249             msg_Err( p_this, "failed to use the SOCKS server" );
250             net_Close( i_handle );
251             i_handle = -1;
252         }
253
254         free( psz_user );
255         free( psz_pwd );
256     }
257     free( psz_socks );
258
259     return i_handle;
260 }
261
262
263 /*****************************************************************************
264  * __net_ListenTCP:
265  *****************************************************************************
266  * Open TCP passive "listening" socket(s)
267  * This function returns NULL in case of error.
268  *****************************************************************************/
269 int *__net_ListenTCP( vlc_object_t *p_this, const char *psz_host, int i_port )
270 {
271     struct addrinfo hints, *res, *ptr;
272     int             i_val, *pi_handles, i_size;
273
274     memset( &hints, 0, sizeof( hints ) );
275     hints.ai_socktype = SOCK_STREAM;
276     hints.ai_flags = AI_PASSIVE;
277
278     msg_Dbg( p_this, "net: listening to %s port %d", psz_host, i_port );
279
280     i_val = vlc_getaddrinfo( p_this, psz_host, i_port, &hints, &res );
281     if( i_val )
282     {
283         msg_Err( p_this, "cannot resolve %s port %d : %s", psz_host, i_port,
284                  vlc_gai_strerror( i_val ) );
285         return NULL;
286     }
287
288     pi_handles = NULL;
289     i_size = 1;
290
291     for( ptr = res; ptr != NULL; ptr = ptr->ai_next )
292     {
293         int fd, *newpi;
294
295         fd = net_Socket( p_this, ptr->ai_family, ptr->ai_socktype,
296                          ptr->ai_protocol );
297         if( fd == -1 )
298             continue;
299
300         /* Bind the socket */
301         if( bind( fd, ptr->ai_addr, ptr->ai_addrlen ) )
302         {
303 #if defined(WIN32) || defined(UNDER_CE)
304             msg_Warn( p_this, "cannot bind socket (%i)", WSAGetLastError( ) );
305 #else
306             msg_Warn( p_this, "cannot bind socket (%s)", strerror( errno ) );
307 #endif
308             net_Close( fd );
309             continue;
310         }
311
312         /* Listen */
313         if( listen( fd, 100 ) == -1 )
314         {
315 #if defined(WIN32) || defined(UNDER_CE)
316             msg_Err( p_this, "cannot bring socket in listening mode (%i)",
317                      WSAGetLastError());
318 #else
319             msg_Err( p_this, "cannot bring the socket in listening mode (%s)",
320                      strerror( errno ) );
321 #endif
322             net_Close( fd );
323             continue;
324         }
325
326         newpi = (int *)realloc( pi_handles, (++i_size) * sizeof( int ) );
327         if( newpi == NULL )
328         {
329             net_Close( fd );
330             break;
331         }
332         else
333         {
334             newpi[i_size - 2] = fd;
335             pi_handles = newpi;
336         }
337     }
338
339     vlc_freeaddrinfo( res );
340
341     if( pi_handles != NULL )
342         pi_handles[i_size - 1] = -1;
343     return pi_handles;
344 }
345
346 /*****************************************************************************
347  * __net_Accept:
348  *****************************************************************************
349  * Accept a connection on a set of listening sockets and return it
350  *****************************************************************************/
351 int __net_Accept( vlc_object_t *p_this, int *pi_fd, mtime_t i_wait )
352 {
353     vlc_bool_t b_die = p_this->b_die, b_block = (i_wait < 0);
354
355     while( p_this->b_die == b_die )
356     {
357         int i_val = -1, *pi, *pi_end;
358         struct timeval timeout;
359         fd_set fds_r, fds_e;
360
361         pi = pi_fd;
362
363         /* Initialize file descriptor set */
364         FD_ZERO( &fds_r );
365         FD_ZERO( &fds_e );
366
367         for( pi = pi_fd; *pi != -1; pi++ )
368         {
369             int i_fd = *pi;
370
371             if( i_fd > i_val )
372                 i_val = i_fd;
373
374             FD_SET( i_fd, &fds_r );
375             FD_SET( i_fd, &fds_e );
376         }
377         pi_end = pi;
378
379         timeout.tv_sec = 0;
380         timeout.tv_usec = b_block ? 500000 : i_wait;
381
382         i_val = select( i_val + 1, &fds_r, NULL, &fds_e, &timeout );
383         if( ( ( i_val < 0 ) && ( errno == EINTR ) ) || i_val == 0 )
384         {
385             if( b_block )
386                 continue;
387             else
388                 return -1;
389         }
390         else if( i_val < 0 )
391         {
392 #if defined(WIN32) || defined(UNDER_CE)
393             msg_Err( p_this, "network select error (%i)", WSAGetLastError() );
394 #else
395             msg_Err( p_this, "network select error (%s)", strerror( errno ) );
396 #endif
397             return -1;
398         }
399
400         for( pi = pi_fd; *pi != -1; pi++ )
401         {
402             int i_fd = *pi;
403
404             if( !FD_ISSET( i_fd, &fds_r ) && !FD_ISSET( i_fd, &fds_e ) )
405                 continue;
406
407             i_val = accept( i_fd, NULL, 0 );
408             if( i_val < 0 )
409             {
410 #if defined(WIN32) || defined(UNDER_CE)
411                 msg_Err( p_this, "accept failed (%i)", WSAGetLastError() );
412 #else
413                 msg_Err( p_this, "accept failed (%s)", strerror( errno ) );
414 #endif
415             }
416             else
417             {
418                 /*
419                  * This round-robin trick ensures that the first sockets in
420                  * pi_fd won't prevent the last ones from getting accept'ed.
421                  */
422                 --pi_end;
423                 memmove( pi, pi + 1, pi_end - pi );
424                 *pi_end = i_fd;
425                 return i_val;
426             }
427         }
428     }
429
430     return -1;
431 }
432
433
434 /*****************************************************************************
435  * SocksNegociate:
436  *****************************************************************************
437  * Negociate authentication with a SOCKS server.
438  *****************************************************************************/
439 static int SocksNegociate( vlc_object_t *p_obj,
440                            int fd, int i_socks_version,
441                            char *psz_socks_user,
442                            char *psz_socks_passwd )
443 {
444     uint8_t buffer[128+2*256];
445     int i_len;
446     vlc_bool_t b_auth = VLC_FALSE;
447
448     if( i_socks_version != 5 )
449         return VLC_SUCCESS;
450
451     /* We negociate authentication */
452
453     if( psz_socks_user && psz_socks_passwd &&
454         *psz_socks_user && *psz_socks_passwd )
455         b_auth = VLC_TRUE;
456
457     buffer[0] = i_socks_version;    /* SOCKS version */
458     if( b_auth )
459     {
460         buffer[1] = 2;                  /* Number of methods */
461         buffer[2] = 0x00;               /* - No auth required */
462         buffer[3] = 0x02;               /* - USer/Password */
463         i_len = 4;
464     }
465     else
466     {
467         buffer[1] = 1;                  /* Number of methods */
468         buffer[2] = 0x00;               /* - No auth required */
469         i_len = 3;
470     }
471
472     if( net_Write( p_obj, fd, NULL, buffer, i_len ) != i_len )
473         return VLC_EGENERIC;
474     if( net_Read( p_obj, fd, NULL, buffer, 2, VLC_TRUE ) != 2 )
475         return VLC_EGENERIC;
476
477     msg_Dbg( p_obj, "socks: v=%d method=%x", buffer[0], buffer[1] );
478
479     if( buffer[1] == 0x00 )
480     {
481         msg_Dbg( p_obj, "socks: no authentication required" );
482     }
483     else if( buffer[1] == 0x02 )
484     {
485         int i_len1 = __MIN( strlen(psz_socks_user), 255 );
486         int i_len2 = __MIN( strlen(psz_socks_passwd), 255 );
487         msg_Dbg( p_obj, "socks: username/password authentication" );
488
489         /* XXX: we don't support user/pwd > 255 (truncated)*/
490         buffer[0] = i_socks_version;        /* Version */
491         buffer[1] = i_len1;                 /* User length */
492         memcpy( &buffer[2], psz_socks_user, i_len1 );
493         buffer[2+i_len1] = i_len2;          /* Password length */
494         memcpy( &buffer[2+i_len1+1], psz_socks_passwd, i_len2 );
495
496         i_len = 3 + i_len1 + i_len2;
497
498         if( net_Write( p_obj, fd, NULL, buffer, i_len ) != i_len )
499             return VLC_EGENERIC;
500
501         if( net_Read( p_obj, fd, NULL, buffer, 2, VLC_TRUE ) != 2 )
502             return VLC_EGENERIC;
503
504         msg_Dbg( p_obj, "socks: v=%d status=%x", buffer[0], buffer[1] );
505         if( buffer[1] != 0x00 )
506         {
507             msg_Err( p_obj, "socks: authentication rejected" );
508             return VLC_EGENERIC;
509         }
510     }
511     else
512     {
513         if( b_auth )
514             msg_Err( p_obj, "socks: unsupported authentication method %x",
515                      buffer[0] );
516         else
517             msg_Err( p_obj, "socks: authentification needed" );
518         return VLC_EGENERIC;
519     }
520
521     return VLC_SUCCESS;
522 }
523
524 /*****************************************************************************
525  * SocksHandshakeTCP:
526  *****************************************************************************
527  * Open a TCP connection using a SOCKS server and return a handle (RFC 1928)
528  *****************************************************************************/
529 static int SocksHandshakeTCP( vlc_object_t *p_obj,
530                               int fd,
531                               int i_socks_version,
532                               char *psz_socks_user, char *psz_socks_passwd,
533                               const char *psz_host, int i_port )
534 {
535     uint8_t buffer[128+2*256];
536
537     if( i_socks_version != 4 && i_socks_version != 5 )
538     {
539         msg_Warn( p_obj, "invalid socks protocol version %d", i_socks_version );
540         i_socks_version = 5;
541     }
542
543     if( i_socks_version == 5 && 
544         SocksNegociate( p_obj, fd, i_socks_version,
545                         psz_socks_user, psz_socks_passwd ) )
546         return VLC_EGENERIC;
547
548     if( i_socks_version == 4 )
549     {
550         struct addrinfo hints = { 0 }, *p_res;
551
552         /* v4 only support ipv4 */
553         hints.ai_family = AF_INET;
554         if( vlc_getaddrinfo( p_obj, psz_host, 0, &hints, &p_res ) )
555             return VLC_EGENERIC;
556
557         buffer[0] = i_socks_version;
558         buffer[1] = 0x01;               /* CONNECT */
559         SetWBE( &buffer[2], i_port );   /* Port */
560         memcpy( &buffer[4],             /* Address */
561                 &((struct sockaddr_in *)(p_res->ai_addr))->sin_addr, 4 );
562         vlc_freeaddrinfo( p_res );
563
564         buffer[8] = 0;                  /* Empty user id */
565
566         if( net_Write( p_obj, fd, NULL, buffer, 9 ) != 9 )
567             return VLC_EGENERIC;
568         if( net_Read( p_obj, fd, NULL, buffer, 8, VLC_TRUE ) != 8 )
569             return VLC_EGENERIC;
570
571         msg_Dbg( p_obj, "socks: v=%d cd=%d",
572                  buffer[0], buffer[1] );
573
574         if( buffer[1] != 90 )
575             return VLC_EGENERIC;
576     }
577     else if( i_socks_version == 5 )
578     {
579         int i_hlen = __MIN(strlen( psz_host ), 255);
580         int i_len;
581
582         buffer[0] = i_socks_version;    /* Version */
583         buffer[1] = 0x01;               /* Cmd: connect */
584         buffer[2] = 0x00;               /* Reserved */
585         buffer[3] = 3;                  /* ATYP: for now domainname */
586
587         buffer[4] = i_hlen;
588         memcpy( &buffer[5], psz_host, i_hlen );
589         SetWBE( &buffer[5+i_hlen], i_port );
590
591         i_len = 5 + i_hlen + 2;
592
593
594         if( net_Write( p_obj, fd, NULL, buffer, i_len ) != i_len )
595             return VLC_EGENERIC;
596
597         /* Read the header */
598         if( net_Read( p_obj, fd, NULL, buffer, 5, VLC_TRUE ) != 5 )
599             return VLC_EGENERIC;
600
601         msg_Dbg( p_obj, "socks: v=%d rep=%d atyp=%d",
602                  buffer[0], buffer[1], buffer[3] );
603
604         if( buffer[1] != 0x00 )
605         {
606             msg_Err( p_obj, "socks: CONNECT request failed\n" );
607             return VLC_EGENERIC;
608         }
609
610         /* Read the remaining bytes */
611         if( buffer[3] == 0x01 )
612             i_len = 4-1 + 2;
613         else if( buffer[3] == 0x03 )
614             i_len = buffer[4] + 2;
615         else if( buffer[3] == 0x04 )
616             i_len = 16-1+2;
617         else 
618             return VLC_EGENERIC;
619
620         if( net_Read( p_obj, fd, NULL, buffer, i_len, VLC_TRUE ) != i_len )
621             return VLC_EGENERIC;
622     }
623
624     return VLC_SUCCESS;
625 }
626
627 void net_ListenClose( int *pi_fd )
628 {
629     if( pi_fd != NULL )
630     {
631         int *pi;
632
633         for( pi = pi_fd; *pi != -1; pi++ )
634             net_Close( *pi );
635         free( pi_fd );
636     }
637 }