]> git.sesse.net Git - vlc/blob - src/network/acl.c
06eba088b48d7c369b6732c6c9689fbae07936f9
[vlc] / src / network / acl.c
1 /*****************************************************************************
2  * acl.c:
3  *****************************************************************************
4  * Copyright © 2005-2007 Rémi Denis-Courmont
5  * $Id$
6  *
7  * Authors: Rémi Denis-Courmont <rem # videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <vlc/vlc.h>
28
29 #include <ctype.h>
30 #include <vlc_acl.h>
31
32 #include <errno.h>
33
34 #include <vlc_network.h>
35 #include <vlc_charset.h>
36
37 /* FIXME: rwlock on acl, but libvlc doesn't implement rwlock */
38 typedef struct vlc_acl_entry_t
39 {
40     uint8_t    host[17];
41     uint8_t    i_bytes_match;
42     uint8_t    i_bits_mask;
43     vlc_bool_t b_allow;
44 } vlc_acl_entry_t;
45
46 struct vlc_acl_t
47 {
48     vlc_object_t    *p_owner;
49     unsigned         i_size;
50     vlc_acl_entry_t *p_entries;
51     vlc_bool_t       b_allow_default;
52 };
53
54 static int ACL_Resolve( vlc_object_t *p_this, uint8_t *p_bytes,
55                         const char *psz_ip )
56 {
57     struct addrinfo hints, *res;
58     int i_family;
59
60     memset (&hints, 0, sizeof (hints));
61     hints.ai_socktype = SOCK_STREAM; /* doesn't matter */
62     hints.ai_flags = AI_NUMERICHOST;
63
64     if( vlc_getaddrinfo( p_this, psz_ip, 0, &hints, &res ) )
65     {
66         msg_Err( p_this, "invalid IP address %s", psz_ip );
67         return -1;
68     }
69
70     p_bytes[16] = 0; /* avoids overflowing when i_bytes_match = 16 */
71
72     i_family = res->ai_addr->sa_family;
73     switch( i_family )
74     {
75         case AF_INET:
76         {
77             struct sockaddr_in *addr;
78
79             addr = (struct sockaddr_in *)res->ai_addr;
80             memset( p_bytes, 0, 12 );
81             memcpy( p_bytes + 12, &addr->sin_addr, 4 );
82             break;
83         }
84
85 #if defined (HAVE_GETADDRINFO) || defined (WIN32)
86         /* unfortunately many people define AF_INET6
87            though they don't have struct sockaddr_in6 */
88         case AF_INET6:
89         {
90             struct sockaddr_in6 *addr;
91
92             addr = (struct sockaddr_in6 *)res->ai_addr;
93             memcpy( p_bytes, &addr->sin6_addr, 16 );
94             break;
95         }
96 #endif
97
98         default:
99             msg_Err( p_this, "unknown address family" );
100             vlc_freeaddrinfo( res );
101             return -1;
102     }
103
104     vlc_freeaddrinfo( res );
105     return i_family;
106 }
107
108
109 /**
110  * Check if a given address passes an access control list.
111  *
112  * @param p_acl pre-existing ACL to match the address against
113  * @param psz_ip numeric IPv4/IPv6 address
114  *
115  * @return 0 if the first matching ACL entry is an access grant,
116  * 1 if the first matching ACL entry is a denial of access,
117  * -1 on error.
118  */
119 int ACL_Check( vlc_acl_t *p_acl, const char *psz_ip )
120 {
121     const vlc_acl_entry_t *p_cur, *p_end;
122     uint8_t host[17];
123
124     if( p_acl == NULL )
125         return -1;
126
127     p_cur = p_acl->p_entries;
128     p_end = p_cur + p_acl->i_size;
129
130     if( ACL_Resolve( p_acl->p_owner, host, psz_ip ) < 0 )
131         return -1;
132
133     while (p_cur < p_end)
134     {
135         unsigned i;
136
137         i = p_cur->i_bytes_match;
138         if( (memcmp( p_cur->host, host, i ) == 0)
139          && (((p_cur->host[i] ^ host[i]) & p_cur->i_bits_mask) == 0) )
140             return !p_cur->b_allow;
141
142         p_cur++;
143     }
144
145     return !p_acl->b_allow_default;
146 }
147
148 /**
149  * Adds an item to an ACL.
150  * Items are always matched in the same order as they are added.
151  */
152 int ACL_AddNet( vlc_acl_t *p_acl, const char *psz_ip, int i_len,
153                 vlc_bool_t b_allow )
154 {
155     vlc_acl_entry_t *p_ent;
156     unsigned i_size;
157     div_t d;
158     int i_family;
159
160     i_size = p_acl->i_size;
161     p_ent = (vlc_acl_entry_t *)realloc( p_acl->p_entries,
162                                         ++p_acl->i_size * sizeof( *p_ent ) );
163
164     if( p_ent == NULL )
165         return -1;
166
167     p_acl->p_entries = p_ent;
168     p_ent += i_size;
169
170     i_family = ACL_Resolve( p_acl->p_owner, p_ent->host, psz_ip );
171     if( i_family < 0 )
172     {
173         /*
174          * I'm lazy : memory space will be re-used in the next ACL_Add call...
175          * or not.
176          */
177         p_acl->i_size--;
178         return -1;
179     }
180
181     if( i_len >= 0 )
182     {
183         if( i_family == AF_INET )
184             i_len += 96;
185
186         if( i_len > 128 )
187             i_len = 128;
188         else
189         if( i_len < 0 )
190             i_len = 0;
191     }
192     else
193         i_len = 128; /* ACL_AddHost */
194
195     d = div( i_len, 8 );
196     p_ent->i_bytes_match = d.quot;
197     p_ent->i_bits_mask = 0xff << (8 - d.rem);
198
199     p_ent->b_allow = b_allow;
200     return 0;
201 }
202
203
204 /**
205  * Creates an empty ACL.
206  *
207  * @param b_allow whether to grant (VLC_TRUE) or deny (VLC_FALSE) access
208  * by default (ie if none of the ACL entries matched).
209  *
210  * @return an ACL object. NULL in case of error.
211  */
212 vlc_acl_t *__ACL_Create( vlc_object_t *p_this, vlc_bool_t b_allow )
213 {
214     vlc_acl_t *p_acl;
215
216     p_acl = (vlc_acl_t *)malloc( sizeof( *p_acl ) );
217     if( p_acl == NULL )
218         return NULL;
219
220     vlc_object_yield( p_this );
221     p_acl->p_owner = p_this;
222     p_acl->i_size = 0;
223     p_acl->p_entries = NULL;
224     p_acl->b_allow_default = b_allow;
225
226     return p_acl;
227 }
228
229
230 /**
231  * Perform a deep copy of an existing ACL.
232  *
233  * @param p_this object to attach the copy to.
234  * @param p_acl ACL object to be copied.
235  *
236  * @return a new ACL object, or NULL on error.
237  */
238 vlc_acl_t *__ACL_Duplicate( vlc_object_t *p_this, const vlc_acl_t *p_acl )
239 {
240     vlc_acl_t *p_dupacl;
241
242     if( p_acl == NULL )
243         return NULL;
244
245     p_dupacl = (vlc_acl_t *)malloc( sizeof( *p_dupacl ) );
246     if( p_dupacl == NULL )
247         return NULL;
248
249     if( p_acl->i_size )
250     {
251         p_dupacl->p_entries = (vlc_acl_entry_t *)
252             malloc( p_acl->i_size * sizeof( vlc_acl_entry_t ) );
253
254         if( p_dupacl->p_entries == NULL )
255         {
256             free( p_dupacl );
257             return NULL;
258         }
259
260         memcpy( p_dupacl->p_entries, p_acl->p_entries,
261                 p_acl->i_size * sizeof( vlc_acl_entry_t ) );
262     }
263     else
264         p_dupacl->p_entries = NULL;
265
266     vlc_object_yield( p_this );
267     p_dupacl->p_owner = p_this;
268     p_dupacl->i_size = p_acl->i_size;
269     p_dupacl->b_allow_default = p_acl->b_allow_default;
270
271     return p_dupacl;
272 }
273
274
275 /**
276  * Releases all resources associated with an ACL object.
277  */
278 void ACL_Destroy( vlc_acl_t *p_acl )
279 {
280     if( p_acl != NULL )
281     {
282         if( p_acl->p_entries != NULL )
283             free( p_acl->p_entries );
284
285         vlc_object_release( p_acl->p_owner );
286         free( p_acl );
287     }
288 }
289
290
291 /**
292  * Reads ACL entries from a file.
293  *
294  * @param p_acl ACL object in which to insert parsed entries.
295  * @param psz_patch filename from which to parse entries.
296  *
297  * @return 0 on success, -1 on error.
298  */
299 int ACL_LoadFile( vlc_acl_t *p_acl, const char *psz_path )
300 {
301     FILE *file;
302
303     if( p_acl == NULL )
304         return -1;
305
306     file = utf8_fopen( psz_path, "r" );
307     if( file == NULL )
308         return -1;
309
310     msg_Dbg( p_acl->p_owner, "find .hosts in dir=%s", psz_path );
311
312     while( !feof( file ) )
313     {
314         char line[1024], *psz_ip, *ptr;
315
316         if( fgets( line, sizeof( line ), file ) == NULL )
317         {
318             if( ferror( file ) )
319             {
320                 msg_Err( p_acl->p_owner, "error reading %s : %s\n", psz_path,
321                         strerror( errno ) );
322                 goto error;
323             }
324             continue;
325         }
326
327         /* fgets() is cool : never overflow, always nul-terminate */
328         psz_ip = line;
329
330         /* skips blanks - cannot overflow given '\0' is not space */
331         while( isspace( *psz_ip ) )
332             psz_ip++;
333
334         if( *psz_ip == '\0' ) /* empty/blank line */
335             continue;
336
337         ptr = strchr( psz_ip, '\n' );
338         if( ptr == NULL )
339         {
340             msg_Warn( p_acl->p_owner, "skipping overly long line in %s\n",
341                       psz_path);
342             do
343             {
344                 if( fgets( line, sizeof( line ), file ) == NULL )
345                 {
346                      if( ferror( file ) )
347                      {
348                          msg_Err( p_acl->p_owner, "error reading %s : %s\n",
349                                   psz_path, strerror( errno ) );
350                      }
351                      goto error;
352                 }
353             }
354             while( strchr( line, '\n' ) == NULL);
355
356             continue; /* skip unusable line */
357         }
358
359         /* skips comment-only line */
360         if( *psz_ip == '#' )
361             continue;
362
363         /* looks for first space, CR, LF, etc. or end-of-line comment */
364         /* (there is at least a linefeed) */
365         for( ptr = psz_ip; ( *ptr != '#' ) && !isspace( *ptr ); ptr++ );
366
367         *ptr = '\0';
368
369         msg_Dbg( p_acl->p_owner, "restricted to %s", psz_ip );
370
371         ptr = strchr( psz_ip, '/' );
372         if( ptr != NULL )
373             *ptr++ = '\0'; /* separate address from mask length */
374
375         if( (ptr != NULL)
376             ? ACL_AddNet( p_acl, psz_ip, atoi( ptr ), VLC_TRUE ) 
377             : ACL_AddHost( p_acl, psz_ip, VLC_TRUE ) )
378         {
379             msg_Err( p_acl->p_owner, "cannot add ACL from %s", psz_path );
380             goto error;
381         }
382     }
383
384     fclose( file );
385     return 0;
386
387 error:
388     fclose( file );
389     return -1;
390 }
391