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