1
/* $Id: resolv_conf_parser.c $ */
3
* resolv_conf_parser.c - parser of resolv.conf resolver(5)
7
* Copyright (C) 2014 Oracle Corporation
9
* This file is part of VirtualBox Open Source Edition (OSE), as
10
* available from http://www.virtualbox.org. This file is free software;
11
* you can redistribute it and/or modify it under the terms of the GNU
12
* General Public License (GPL) as published by the Free Software
13
* Foundation, in version 2 as it comes in the "COPYING" file of the
14
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18
#include <iprt/assert.h>
19
#include <iprt/initterm.h>
21
#include <iprt/string.h>
22
#include <iprt/stream.h>
23
#include <iprt/thread.h>
25
#include <arpa/inet.h>
29
#include "resolv_conf_parser.h"
31
/* XXX: it's required to add the aliases for keywords and
32
* types to handle conditions more clearly */
35
tok_eof = -1, /* EOF */
36
tok_string = -2, /* string */
37
tok_number = -3, /* number */
38
tok_ipv4 = -4, /* ipv4 */
39
tok_ipv4_port = -5, /* ipv4 port */
40
tok_ipv6 = -6, /* ipv6 */
41
tok_ipv6_port = -7, /* ipv6 port */
43
tok_nameserver = -8, /* nameserver */
44
tok_port = -9, /* port, Mac OSX specific */
45
tok_domain = -10, /* domain */
46
tok_search = -11, /* search */
47
tok_search_order = -12, /* search order */
48
tok_sortlist = -13, /* sortlist */
49
tok_timeout = -14, /* timeout */
50
tok_options = -15, /* options */
51
tok_option = -16, /* option */
52
tok_comment = -17, /* comment */
56
#define RCP_BUFFER_SIZE 256
61
enum RCP_TOKEN rcpp_token;
62
char rcpp_str_buffer[RCP_BUFFER_SIZE];
63
struct rcp_state *rcpp_state;
64
PRTSTREAM rcpp_stream;
68
#define GETCHAR(parser) (RTStrmGetCh((parser)->rcpp_stream))
72
#define PARSER_STOP(tok, parser, ptr) ( (tok) != EOF \
73
&& (((ptr) - (parser)->rcpp_str_buffer) != (RCP_BUFFER_SIZE - 1)))
74
#define PARSER_BUFFER_EXCEEDED(parser, ptr) \
76
if (((ptr) - (parser)->rcpp_str_buffer) == (RCP_BUFFER_SIZE - 1)) { \
81
static int rcp_get_token(struct rcp_parser *parser)
88
tok = GETCHAR(parser);
90
ptr = parser->rcpp_str_buffer;
92
/* tok can't be ipv4 */
94
int xdigit, digit, dot_number;
95
RT_ZERO(parser->rcpp_str_buffer);
102
tok = GETCHAR(parser);
104
if (!isalnum(tok) && tok != ':' && tok != '.' && tok != '-' && tok != '_')
108
* if before ':' there were only [0-9][a-f][A-F],
109
* then it can't be option.
111
xdigit &= (isxdigit(tok) || (tok == ':'));
113
* We want hint to differ ipv4 and network name.
115
digit &= (isdigit(tok) || (tok == '.'));
125
tok = GETCHAR(parser);
130
} while(PARSER_STOP(tok, parser, ptr) && (tok == ':' || tok == '.' || isxdigit(tok)));
132
PARSER_BUFFER_EXCEEDED(parser, ptr);
137
return tok_ipv6_port;
140
/* eats rest of the token */
144
tok = GETCHAR(parser);
145
} while( PARSER_STOP(tok, parser, ptr)
146
&& (isalnum(tok) || tok == '.' || tok == '_' || tok == '-'));
148
PARSER_BUFFER_EXCEEDED(parser, ptr);
154
/* XXX: need further experiments */
155
return tok_option; /* option with value */
162
if (tok == '.') dot_number++;
165
digit &= (isdigit(tok) || (tok == '.'));
166
tok = GETCHAR(parser);
167
} while( PARSER_STOP(tok, parser, ptr)
168
&& (isalnum(tok) || tok == '.' || tok == '_' || tok == '-'));
170
PARSER_BUFFER_EXCEEDED(parser, ptr);
172
if (dot_number == 3 && digit)
174
else if (dot_number == 4 && digit)
175
return tok_ipv4_port;
179
} while( PARSER_STOP(tok, parser, ptr)
180
&& (isalnum(tok) || tok == ':' || tok == '.' || tok == '-' || tok == '_'));
182
PARSER_BUFFER_EXCEEDED(parser, ptr);
186
if (RTStrCmp(parser->rcpp_str_buffer, "nameserver") == 0)
187
return tok_nameserver;
188
if (RTStrCmp(parser->rcpp_str_buffer, "port") == 0)
190
if (RTStrCmp(parser->rcpp_str_buffer, "domain") == 0)
192
if (RTStrCmp(parser->rcpp_str_buffer, "search") == 0)
194
if (RTStrCmp(parser->rcpp_str_buffer, "search_order") == 0)
195
return tok_search_order;
196
if (RTStrCmp(parser->rcpp_str_buffer, "sortlist") == 0)
198
if (RTStrCmp(parser->rcpp_str_buffer, "timeout") == 0)
200
if (RTStrCmp(parser->rcpp_str_buffer, "options") == 0)
206
if (tok == EOF) return tok_eof;
211
tok = GETCHAR(parser);
212
} while (tok != EOF && tok != '\r' && tok != '\n');
214
if (tok == EOF) return tok_eof;
222
#undef PARSER_BUFFER_EXCEEDED
225
* nameserverexpr ::= 'nameserver' ip+
226
* @note: resolver(5) ip ::= (ipv4|ipv6)(.number)?
228
static enum RCP_TOKEN rcp_parse_nameserver(struct rcp_parser *parser)
230
enum RCP_TOKEN tok = rcp_get_token(parser); /* eats 'nameserver' */
232
if ( ( tok != tok_ipv4
233
&& tok != tok_ipv4_port
235
&& tok != tok_ipv6_port)
239
while ( tok == tok_ipv4
240
|| tok == tok_ipv4_port
242
|| tok == tok_ipv6_port)
244
struct rcp_state *st;
248
Assert(parser->rcpp_state);
250
st = parser->rcpp_state;
252
/* It's still valid resolv.conf file, just rest of the nameservers should be ignored */
253
if (st->rcps_num_nameserver >= RCPS_MAX_NAMESERVERS)
254
return rcp_get_token(parser);
256
address = &st->rcps_nameserver[st->rcps_num_nameserver];
257
str_address = &st->rcps_nameserver_str_buffer[st->rcps_num_nameserver * RCPS_IPVX_SIZE];
259
if ( tok == tok_ipv4_port
260
|| ( tok == tok_ipv6_port
261
&& (st->rcps_flags & RCPSF_IGNORE_IPV6) == 0))
263
char *ptr = &parser->rcpp_str_buffer[strlen(parser->rcpp_str_buffer)];
264
while (*(--ptr) != '.');
266
address->uPort = RTStrToUInt16(ptr + 1);
268
if (address->uPort == 0) return tok_error;
272
* if we on Darwin upper code will cut off port if it's.
274
if ((st->rcps_flags & RCPSF_NO_STR2IPCONV) != 0)
276
if (strlen(parser->rcpp_str_buffer) > RCPS_IPVX_SIZE)
279
strcpy(str_address, parser->rcpp_str_buffer);
281
st->rcps_str_nameserver[st->rcps_num_nameserver] = str_address;
291
int rc = RTNetStrToIPv4Addr(parser->rcpp_str_buffer, &address->uAddr.IPv4);
292
if (RT_FAILURE(rc)) return tok_error;
294
address->enmType = RTNETADDRTYPE_IPV4;
303
if ((st->rcps_flags & RCPSF_IGNORE_IPV6) != 0)
304
return rcp_get_token(parser);
306
rc = inet_pton(AF_INET6, parser->rcpp_str_buffer,
307
&address->uAddr.IPv6);
311
address->enmType = RTNETADDRTYPE_IPV6;
315
default: /* loop condition doesn't let enter enything */
316
AssertMsgFailed(("shouldn't ever happen tok:%d, %s", tok,
317
isprint(tok) ? parser->rcpp_str_buffer : "#"));
322
st->rcps_num_nameserver++;
323
tok = rcp_get_token(parser);
329
* portexpr ::= 'port' [0-9]+
331
static enum RCP_TOKEN rcp_parse_port(struct rcp_parser *parser)
333
struct rcp_state *st;
334
enum RCP_TOKEN tok = rcp_get_token(parser); /* eats 'port' */
336
Assert(parser->rcpp_state);
337
st = parser->rcpp_state;
339
if ( tok != tok_number
343
st->rcps_port = RTStrToUInt16(parser->rcpp_str_buffer);
345
if (st->rcps_port == 0)
348
return rcp_get_token(parser);
352
* domainexpr ::= 'domain' string
354
static enum RCP_TOKEN rcp_parse_domain(struct rcp_parser *parser)
356
struct rcp_state *st;
357
enum RCP_TOKEN tok = rcp_get_token(parser); /* eats 'domain' */
359
Assert(parser->rcpp_state);
360
st = parser->rcpp_state;
363
* It's nowhere specified how resolver should react on dublicats
364
* of 'domain' declarations, let's assume that resolv.conf is broken.
368
|| st->rcps_domain != NULL)
371
strcpy(st->rcps_domain_buffer, parser->rcpp_str_buffer);
373
* We initialize this pointer in place, just make single pointer check
374
* in 'domain'-less resolv.conf.
376
st->rcps_domain = st->rcps_domain_buffer;
378
return rcp_get_token(parser);
382
* searchexpr ::= 'search' (string)+
383
* @note: resolver (5) Mac OSX:
384
* "The search list is currently limited to six domains with a total of 256 characters."
385
* @note: resolv.conf (5) Linux:
386
* "The search list is currently limited to six domains with a total of 256 characters."
387
* @note: 'search' parameter could contains numbers only hex or decimal, 1c1e or 111
389
static enum RCP_TOKEN rcp_parse_search(struct rcp_parser *parser)
391
unsigned i, len, trailing;
393
struct rcp_state *st;
394
enum RCP_TOKEN tok = rcp_get_token(parser); /* eats 'search' */
396
Assert(parser->rcpp_state);
397
st = parser->rcpp_state;
403
/* just ignore "too many search list" */
404
if (st->rcps_num_searchlist >= RCPS_MAX_SEARCHLIST)
405
return rcp_get_token(parser);
407
/* we don't want accept keywords */
408
if (tok <= tok_nameserver)
411
/* if there're several entries of "search" we compose them together */
412
i = st->rcps_num_searchlist;
414
trailing = RCPS_BUFFER_SIZE;
417
ptr = st->rcps_searchlist[i - 1];
418
trailing = RCPS_BUFFER_SIZE - (ptr -
419
st->rcps_searchlist_buffer + strlen(ptr) + 1);
424
len = strlen(parser->rcpp_str_buffer);
426
if (len + 1 > trailing)
427
break; /* not enough room for new entry */
429
if (i >= RCPS_MAX_SEARCHLIST)
430
break; /* not enought free entries for 'search' items */
432
ptr = st->rcps_searchlist_buffer + RCPS_BUFFER_SIZE - trailing;
433
strcpy(ptr, parser->rcpp_str_buffer);
435
trailing -= len + 1; /* 1 reserved for '\0' */
437
st->rcps_searchlist[i++] = ptr;
438
tok = rcp_get_token(parser);
443
|| tok <= tok_nameserver)
447
st->rcps_num_searchlist = i;
453
* expr ::= nameserverexpr | expr
454
* ::= portexpr | expr
455
* ::= domainexpr | expr
456
* ::= searchexpr | expr
457
* ::= searchlistexpr | expr
458
* ::= search_orderexpr | expr
459
* ::= timeoutexpr | expr
460
* ::= optionsexpr | expr
462
static int rcp_parse_primary(struct rcp_parser *parser)
465
tok = rcp_get_token(parser);
467
while( tok != tok_eof
473
tok = rcp_parse_nameserver(parser);
476
tok = rcp_parse_port(parser);
479
tok = rcp_parse_domain(parser);
482
tok = rcp_parse_search(parser);
485
tok = rcp_get_token(parser);
489
if (tok == tok_error)
496
int rcp_parse(struct rcp_state* state, const char *filename)
501
struct rcp_parser parser;
502
flags = state->rcps_flags;
507
state->rcps_flags = flags;
509
parser.rcpp_state = state;
512
* for debugging need: with RCP_STANDALONE it's possible
513
* to run simplefied scenarious like
515
* # cat /etc/resolv.conf | rcp-test-0
517
* # process launch -i /etc/resolv.conf
519
#ifdef RCP_STANDALONE
520
if (filename == NULL)
521
parser.rcpp_stream = g_pStdIn;
523
if (filename == NULL)
528
rc = RTStrmOpen(filename, "r", &parser.rcpp_stream);
529
if (RT_FAILURE(rc)) return -1;
532
rc = rcp_parse_primary(&parser);
534
if (filename != NULL)
535
RTStrmClose(parser.rcpp_stream);
542
* port recolv.conf's option and IP.port are Mac OSX extentions, there're no need to care on
545
if (state->rcps_port == 0)
546
state->rcps_port = 53;
548
for(i = 0; (state->rcps_flags & RCPSF_NO_STR2IPCONV) == 0
549
&& i != RCPS_MAX_NAMESERVERS; ++i)
551
RTNETADDR *addr = &state->rcps_nameserver[i];
553
if (addr->uPort == 0)
554
addr->uPort = state->rcps_port;
558
if ( state->rcps_domain == NULL
559
&& state->rcps_searchlist[0] != NULL)
560
state->rcps_domain = state->rcps_searchlist[0];