diff options
Diffstat (limited to 'src/VBox/Devices/Network/slirp/resolv_conf_parser.c')
-rw-r--r-- | src/VBox/Devices/Network/slirp/resolv_conf_parser.c | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/src/VBox/Devices/Network/slirp/resolv_conf_parser.c b/src/VBox/Devices/Network/slirp/resolv_conf_parser.c new file mode 100644 index 00000000..dd5fb825 --- /dev/null +++ b/src/VBox/Devices/Network/slirp/resolv_conf_parser.c @@ -0,0 +1,539 @@ +/* $Id: resolv_conf_parser.c $ */ +/** @file + * resolv_conf_parser.c - parser of resolv.conf resolver(5) + */ + +/* + * Copyright (C) 2014 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/net.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/thread.h> + +#include <arpa/inet.h> + +#include <ctype.h> + +#include "resolv_conf_parser.h" + +enum RCP_TOKEN +{ + tok_eof = -1, /* EOF */ + tok_string = -2, /* string */ + tok_number = -3, /* number */ + tok_ipv4 = -4, /* ipv4 */ + tok_ipv4_port = -5, /* ipv4 port */ + tok_ipv6 = -6, /* ipv6 */ + tok_ipv6_port = -7, /* ipv6 port */ + tok_nameserver = -8, /* nameserver */ + tok_port = -9, /* port, Mac OSX specific */ + tok_domain = -10, /* domain */ + tok_search = -11, /* search */ + tok_search_order = -12, /* search order */ + tok_sortlist = -13, /* sortlist */ + tok_timeout = -14, /* timeout */ + tok_options = -15, /* options */ + tok_option = -16, /* option */ + tok_comment = -17, /* comment */ + tok_error = -20 +}; + +#define RCP_BUFFER_SIZE 256 + + +struct rcp_parser +{ + enum RCP_TOKEN rcpp_token; + char rcpp_str_buffer[RCP_BUFFER_SIZE]; + struct rcp_state *rcpp_state; + PRTSTREAM rcpp_stream; +}; + + +#define GETCHAR(parser) (RTStrmGetCh((parser)->rcpp_stream)) +#define EOF (-1) + + +#define PARSER_STOP(tok, parser, ptr) ( (tok) != EOF \ + && (((ptr) - (parser)->rcpp_str_buffer) != (RCP_BUFFER_SIZE - 1))) +#define PARSER_BUFFER_EXCEEDED(parser, ptr) \ + do { \ + if (((ptr) - (parser)->rcpp_str_buffer) == (RCP_BUFFER_SIZE - 1)) { \ + return tok_error; \ + } \ + }while(0); + +static int rcp_get_token(struct rcp_parser *parser) +{ + char tok = ' '; + char *ptr; + size_t ptr_len; + + while (isspace(tok)) + tok = GETCHAR(parser); + + ptr = parser->rcpp_str_buffer; + + /* tok can't be ipv4 */ + if (isalnum(tok)) { + int xdigit, digit, dot_number; + RT_ZERO(parser->rcpp_str_buffer); + + dot_number = 0; + xdigit = 1; + digit = 1; + do { + *ptr++ = tok; + tok = GETCHAR(parser); + + if (!isalnum(tok) && tok != ':' && tok != '.' && tok != '-' && tok != '_') + break; + + /** + * if before ':' there were only [0-9][a-f][A-F], + * then it can't be option. + */ + xdigit &= (isxdigit(tok) || (tok == ':')); + /** + * We want hint to differ ipv4 and network name. + */ + digit &= (isdigit(tok) || (tok == '.')); + + if (tok == ':') + { + if (xdigit == 1) + { + int port = 0; + do + { + *ptr++ = tok; + tok = GETCHAR(parser); + + if (tok == '.') + port++; + + } while(PARSER_STOP(tok, parser, ptr) && (tok == ':' || tok == '.' || isxdigit(tok))); + + PARSER_BUFFER_EXCEEDED(parser, ptr); + + if (port == 0) + return tok_ipv6; + else if (port == 1) + return tok_ipv6_port; + else + { + /* eats rest of the token */ + do + { + *ptr++ = tok; + tok = GETCHAR(parser); + } while( PARSER_STOP(tok, parser, ptr) + && (isalnum(tok) || tok == '.' || tok == '_' || tok == '-')); + + PARSER_BUFFER_EXCEEDED(parser, ptr); + + return tok_string; + } + } + else { + /* XXX: need further experiments */ + return tok_option; /* option with value */ + } + } + + if (tok == '.') + { + do { + if (tok == '.') dot_number++; + + *ptr++ = tok; + digit &= (isdigit(tok) || (tok == '.')); + tok = GETCHAR(parser); + } while( PARSER_STOP(tok, parser, ptr) + && (isalnum(tok) || tok == '.' || tok == '_' || tok == '-')); + + PARSER_BUFFER_EXCEEDED(parser, ptr); + + if (dot_number == 3 && digit) + return tok_ipv4; + else if (dot_number == 4 && digit) + return tok_ipv4_port; + else + return tok_string; + } + } while( PARSER_STOP(tok, parser, ptr) + && (isalnum(tok) || tok == ':' || tok == '.' || tok == '-' || tok == '_')); + + PARSER_BUFFER_EXCEEDED(parser, ptr); + + if (digit || xdigit) + return tok_number; + if (RTStrCmp(parser->rcpp_str_buffer, "nameserver") == 0) + return tok_nameserver; + if (RTStrCmp(parser->rcpp_str_buffer, "port") == 0) + return tok_port; + if (RTStrCmp(parser->rcpp_str_buffer, "domain") == 0) + return tok_domain; + if (RTStrCmp(parser->rcpp_str_buffer, "search") == 0) + return tok_search; + if (RTStrCmp(parser->rcpp_str_buffer, "search_order") == 0) + return tok_search_order; + if (RTStrCmp(parser->rcpp_str_buffer, "sortlist") == 0) + return tok_sortlist; + if (RTStrCmp(parser->rcpp_str_buffer, "timeout") == 0) + return tok_timeout; + if (RTStrCmp(parser->rcpp_str_buffer, "options") == 0) + return tok_options; + + return tok_string; + } + + if (tok == EOF) return tok_eof; + + if (tok == '#') + { + do{ + tok = GETCHAR(parser); + } while (tok != EOF && tok != '\r' && tok != '\n'); + + if (tok == EOF) return tok_eof; + + return tok_comment; + } + return tok; +} + +#undef PARSER_STOP +#undef PARSER_BUFFER_EXCEEDED + +/** + * nameserverexpr ::= 'nameserver' ip+ + * @note: resolver(5) ip ::= (ipv4|ipv6)(.number)? + */ +static enum RCP_TOKEN rcp_parse_nameserver(struct rcp_parser *parser) +{ + enum RCP_TOKEN tok = rcp_get_token(parser); /* eats 'nameserver' */ + + if ( ( tok != tok_ipv4 + && tok != tok_ipv4_port + && tok != tok_ipv6 + && tok != tok_ipv6_port) + || tok == EOF) + return tok_error; + + while ( tok == tok_ipv4 + || tok == tok_ipv4_port + || tok == tok_ipv6 + || tok == tok_ipv6_port) + { + struct rcp_state *st; + RTNETADDR *address; + char *str_address; + + Assert(parser->rcpp_state); + + st = parser->rcpp_state; + + /* It's still valid resolv.conf file, just rest of the nameservers should be ignored */ + if (st->rcps_num_nameserver >= RCPS_MAX_NAMESERVERS) + return rcp_get_token(parser); + + address = &st->rcps_nameserver[st->rcps_num_nameserver]; + str_address = &st->rcps_nameserver_str_buffer[st->rcps_num_nameserver * RCPS_IPVX_SIZE]; + +#ifdef RT_OS_DARWIN + if ( tok == tok_ipv4_port + || ( tok == tok_ipv6_port + && (st->rcps_flags & RCPSF_IGNORE_IPV6) == 0)) + { + char *ptr = &parser->rcpp_str_buffer[strlen(parser->rcpp_str_buffer)]; + while (*(--ptr) != '.'); + *ptr = '\0'; + address->uPort = RTStrToUInt16(ptr + 1); + + if (address->uPort == 0) return tok_error; + } +#endif + /** + * if we on Darwin upper code will cut off port if it's. + */ + if ((st->rcps_flags & RCPSF_NO_STR2IPCONV) != 0) + { + if (strlen(parser->rcpp_str_buffer) > RCPS_IPVX_SIZE) + return tok_error; + + strcpy(str_address, parser->rcpp_str_buffer); + + st->rcps_str_nameserver[st->rcps_num_nameserver] = str_address; + + goto loop_prolog; + } + + switch (tok) + { + case tok_ipv4: + case tok_ipv4_port: + { + int rc = RTNetStrToIPv4Addr(parser->rcpp_str_buffer, &address->uAddr.IPv4); + if (RT_FAILURE(rc)) return tok_error; + + address->enmType = RTNETADDRTYPE_IPV4; + } + + break; + case tok_ipv6: + case tok_ipv6_port: + { + int rc; + + if ((st->rcps_flags & RCPSF_IGNORE_IPV6) != 0) + return rcp_get_token(parser); + + rc = inet_pton(AF_INET6, parser->rcpp_str_buffer, + &address->uAddr.IPv6); + if (rc == -1) + return tok_error; + + address->enmType = RTNETADDRTYPE_IPV6; + } + + break; + default: /* loop condition doesn't let enter enything */ + AssertMsgFailed(("shouldn't ever happen tok:%d, %s", tok, + isprint(tok) ? parser->rcpp_str_buffer : "#")); + break; + } + + loop_prolog: + st->rcps_num_nameserver++; + tok = rcp_get_token(parser); + } + return tok; +} + +/** + * portexpr ::= 'port' [0-9]+ + */ +static enum RCP_TOKEN rcp_parse_port(struct rcp_parser *parser) +{ + struct rcp_state *st; + enum RCP_TOKEN tok = rcp_get_token(parser); /* eats 'port' */ + + Assert(parser->rcpp_state); + st = parser->rcpp_state; + + if ( tok != tok_number + || tok == tok_eof) + return tok_error; + + st->rcps_port = RTStrToUInt16(parser->rcpp_str_buffer); + + if (st->rcps_port == 0) + return tok_error; + + return rcp_get_token(parser); +} + +/** + * domainexpr ::= 'domain' string + */ +static enum RCP_TOKEN rcp_parse_domain(struct rcp_parser *parser) +{ + struct rcp_state *st; + enum RCP_TOKEN tok = rcp_get_token(parser); /* eats 'domain' */ + + Assert(parser->rcpp_state); + st = parser->rcpp_state; + + /** + * It's nowhere specified how resolver should react on dublicats + * of 'domain' declarations, let's assume that resolv.conf is broken. + */ + if ( tok == tok_eof + || tok == tok_error + || st->rcps_domain != NULL) + return tok_error; + + strcpy(st->rcps_domain_buffer, parser->rcpp_str_buffer); + /** + * We initialize this pointer in place, just make single pointer check + * in 'domain'-less resolv.conf. + */ + st->rcps_domain = st->rcps_domain_buffer; + + return rcp_get_token(parser); +} + +/** + * searchexpr ::= 'search' (string)+ + * @note: resolver (5) Mac OSX: + * "The search list is currently limited to six domains with a total of 256 characters." + * @note: resolv.conf (5) Linux: + * "The search list is currently limited to six domains with a total of 256 characters." + */ +static enum RCP_TOKEN rcp_parse_search(struct rcp_parser *parser) +{ + unsigned i, len, trailing; + char *ptr; + struct rcp_state *st; + enum RCP_TOKEN tok = rcp_get_token(parser); /* eats 'search' */ + + Assert(parser->rcpp_state); + st = parser->rcpp_state; + + /** + * We asume that duplication of search list in resolv.conf isn't correct. + */ + if ( tok == tok_eof + || tok == tok_error + || tok != tok_string + || st->rcps_searchlist[0] != NULL) + return tok_error; + + i = 0; + trailing = RCPS_BUFFER_SIZE; + do { + len = strlen(parser->rcpp_str_buffer); + + if (len + 1 > trailing) + break; /* not enough room for new entry */ + + ptr = st->rcps_searchlist_buffer + RCPS_BUFFER_SIZE - trailing; + strcpy(ptr, parser->rcpp_str_buffer); + + trailing -= len + 1; /* 1 reserved for '\0' */ + + st->rcps_searchlist[i] = ptr; + + } while( (tok = rcp_get_token(parser)) == tok_string + && ++i != RCPS_MAX_SEARCHLIST); + + st->rcps_num_searchlist = i; + + return tok; +} + +/** + * expr ::= nameserverexpr | expr + * ::= portexpr | expr + * ::= domainexpr | expr + * ::= searchexpr | expr + * ::= searchlistexpr | expr + * ::= search_orderexpr | expr + * ::= timeoutexpr | expr + * ::= optionsexpr | expr + */ +static int rcp_parse_primary(struct rcp_parser *parser) +{ + enum RCP_TOKEN tok; + tok = rcp_get_token(parser); + + while( tok != tok_eof + && tok != tok_error) + { + switch (tok) + { + case tok_nameserver: + tok = rcp_parse_nameserver(parser); + break; + case tok_port: + tok = rcp_parse_port(parser); + break; + case tok_domain: + tok = rcp_parse_domain(parser); + break; + case tok_search: + tok = rcp_parse_search(parser); + break; + default: + tok = rcp_get_token(parser); + } + } + + if (tok == tok_error) + return -1; + + return 0; +} + + +int rcp_parse(struct rcp_state* state, const char *filename) +{ + unsigned i; + uint32_t flags; + int rc; + struct rcp_parser parser; + flags = state->rcps_flags; + + RT_ZERO(parser); + RT_ZERO(*state); + + state->rcps_flags = flags; + + parser.rcpp_state = state; + + /** + * for debugging need: with RCP_STANDALONE it's possible + * to run simplefied scenarious like + * + * # cat /etc/resolv.conf | rcp-test-0 + * or in lldb + * # process launch -i /etc/resolv.conf + */ +#ifdef RCP_STANDALONE + if (filename == NULL) + parser.rcpp_stream = g_pStdIn; +#else + if (filename == NULL) + return -1; +#endif + else + { + rc = RTStrmOpen(filename, "r", &parser.rcpp_stream); + if (RT_FAILURE(rc)) return -1; + } + + rc = rcp_parse_primary(&parser); + + if (filename != NULL) + RTStrmClose(parser.rcpp_stream); + + if (rc == -1) + return -1; + +#ifdef RT_OS_DARWIN + /** + * port recolv.conf's option and IP.port are Mac OSX extentions, there're no need to care on + * other hosts. + */ + if (state->rcps_port == 0) + state->rcps_port = 53; + + for(i = 0; (state->rcps_flags & RCPSF_NO_STR2IPCONV) == 0 + && i != RCPS_MAX_NAMESERVERS; ++i) + { + RTNETADDR *addr = &state->rcps_nameserver[i]; + + if (addr->uPort == 0) + addr->uPort = state->rcps_port; + } +#endif + + if ( state->rcps_domain == NULL + && state->rcps_searchlist[0] != NULL) + state->rcps_domain = state->rcps_searchlist[0]; + + return 0; +} |