/* ntp_scanner.c * * The source code for a simple lexical analyzer. * * Written By: Sachin Kamboj * University of Delaware * Newark, DE 19711 * Copyright (c) 2006 */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "ntpd.h" #include "ntp_config.h" #include "ntpsim.h" #include "ntp_scanner.h" #include "ntp_parser.h" /* ntp_keyword.h declares finite state machine and token text */ #include "ntp_keyword.h" /* SCANNER GLOBAL VARIABLES * ------------------------ */ #define MAX_LEXEME (1024 + 1) /* The maximum size of a lexeme */ char yytext[MAX_LEXEME]; /* Buffer for storing the input text/lexeme */ u_int32 conf_file_sum; /* Simple sum of characters read */ static struct FILE_INFO * lex_stack = NULL; /* CONSTANTS * --------- */ /* SCANNER GLOBAL VARIABLES * ------------------------ */ const char special_chars[] = "{}(),;|="; /* FUNCTIONS * --------- */ static int is_keyword(char *lexeme, follby *pfollowedby); /* * keyword() - Return the keyword associated with token T_ identifier. * See also token_name() for the string-ized T_ identifier. * Example: keyword(T_Server) returns "server" * token_name(T_Server) returns "T_Server" */ const char * keyword( int token ) { size_t i; const char *text; i = token - LOWEST_KEYWORD_ID; if (i < COUNTOF(keyword_text)) text = keyword_text[i]; else text = NULL; return (text != NULL) ? text : "(keyword not found)"; } /* FILE & STRING BUFFER INTERFACE * ------------------------------ * * This set out as a couple of wrapper functions around the standard C * fgetc and ungetc functions in order to include positional * bookkeeping. Alas, this is no longer a good solution with nested * input files and the possibility to send configuration commands via * 'ntpdc' and 'ntpq'. * * Now there are a few functions to maintain a stack of nested input * sources (though nesting is only allowd for disk files) and from the * scanner / parser point of view there's no difference between both * types of sources. * * The 'fgetc()' / 'ungetc()' replacements now operate on a FILE_INFO * structure. Instead of trying different 'ungetc()' strategies for file * and buffer based parsing, we keep the backup char in our own * FILE_INFO structure. This is sufficient, as the parser does *not* * jump around via 'seek' or the like, and there's no need to * check/clear the backup store in other places than 'lex_getch()'. */ /* * Allocate an info structure and attach it to a file. * * Note: When 'mode' is NULL, then the INFO block will be set up to * contain a NULL file pointer, as suited for remote config command * parsing. Otherwise having a NULL file pointer is considered an error, * and a NULL info block pointer is returned to indicate failure! * * Note: We use a variable-sized structure to hold a copy of the file * name (or, more proper, the input source description). This is more * secure than keeping a reference to some other storage that might go * out of scope. */ static struct FILE_INFO * lex_open( const char *path, const char *mode ) { struct FILE_INFO *stream; size_t nnambuf; nnambuf = strlen(path); stream = emalloc_zero(sizeof(*stream) + nnambuf); stream->curpos.nline = 1; stream->backch = EOF; /* copy name with memcpy -- trailing NUL already there! */ memcpy(stream->fname, path, nnambuf); if (NULL != mode) { stream->fpi = fopen(path, mode); if (NULL == stream->fpi) { free(stream); stream = NULL; } } return stream; } /* get next character from buffer or file. This will return any putback * character first; it will also make sure the last line is at least * virtually terminated with a '\n'. */ static int lex_getch( struct FILE_INFO *stream ) { int ch; if (NULL == stream || stream->force_eof) return EOF; if (EOF != stream->backch) { ch = stream->backch; stream->backch = EOF; if (stream->fpi) conf_file_sum += ch; stream->curpos.ncol++; } else if (stream->fpi) { /* fetch next 7-bit ASCII char (or EOF) from file */ while ((ch = fgetc(stream->fpi)) != EOF && ch > SCHAR_MAX) stream->curpos.ncol++; if (EOF != ch) { conf_file_sum += ch; stream->curpos.ncol++; } } else { /* fetch next 7-bit ASCII char from buffer */ const char * scan; scan = &remote_config.buffer[remote_config.pos]; while ((ch = (u_char)*scan) > SCHAR_MAX) { scan++; stream->curpos.ncol++; } if ('\0' != ch) { scan++; stream->curpos.ncol++; } else { ch = EOF; } remote_config.pos = (int)(scan - remote_config.buffer); } /* If the last line ends without '\n', generate one. This * happens most likely on Windows, where editors often have a * sloppy concept of a line. */ if (EOF == ch && stream->curpos.ncol != 0) ch = '\n'; /* update scan position tallies */ if (ch == '\n') { stream->bakpos = stream->curpos; stream->curpos.nline++; stream->curpos.ncol = 0; } return ch; } /* Note: lex_ungetch will fail to track more than one line of push * back. But since it guarantees only one char of back storage anyway, * this should not be a problem. */ static int lex_ungetch( int ch, struct FILE_INFO *stream ) { /* check preconditions */ if (NULL == stream || stream->force_eof) return EOF; if (EOF != stream->backch || EOF == ch) return EOF; /* keep for later reference and update checksum */ stream->backch = (u_char)ch; if (stream->fpi) conf_file_sum -= stream->backch; /* update position */ if (stream->backch == '\n') { stream->curpos = stream->bakpos; stream->bakpos.ncol = -1; } stream->curpos.ncol--; return stream->backch; } /* dispose of an input structure. If the file pointer is not NULL, close * the file. This function does not check the result of 'fclose()'. */ static void lex_close( struct FILE_INFO *stream ) { if (NULL != stream) { if (NULL != stream->fpi) fclose(stream->fpi); free(stream); } } /* INPUT STACK * ----------- * * Nested input sources are a bit tricky at first glance. We deal with * this problem using a stack of input sources, that is, a forward * linked list of FILE_INFO structs. * * This stack is never empty during parsing; while an encounter with EOF * can and will remove nested input sources, removing the last element * in the stack will not work during parsing, and the EOF condition of * the outermost input file remains until the parser folds up. */ static struct FILE_INFO * _drop_stack_do( struct FILE_INFO * head ) { struct FILE_INFO * tail; while (NULL != head) { tail = head->st_next; lex_close(head); head = tail; } return head; } /* Create a singleton input source on an empty lexer stack. This will * fail if there is already an input source, or if the underlying disk * file cannot be opened. * * Returns TRUE if a new input object was successfully created. */ int/*BOOL*/ lex_init_stack( const char * path, const char * mode ) { if (NULL != lex_stack || NULL == path) return FALSE; lex_stack = lex_open(path, mode); return (NULL != lex_stack); } /* This removes *all* input sources from the stack, leaving the head * pointer as NULL. Any attempt to parse in that state is likely to bomb * with segmentation faults or the like. * * In other words: Use this to clean up after parsing, and do not parse * anything until the next 'lex_init_stack()' succeeded. */ void lex_drop_stack() { lex_stack = _drop_stack_do(lex_stack); } /* Flush the lexer input stack: This will nip all input objects on the * stack (but keeps the current top-of-stack) and marks the top-of-stack * as inactive. Any further calls to lex_getch yield only EOF, and it's * no longer possible to push something back. * * Returns TRUE if there is a head element (top-of-stack) that was not * in the force-eof mode before this call. */ int/*BOOL*/ lex_flush_stack() { int retv = FALSE; if (NULL != lex_stack) { retv = !lex_stack->force_eof; lex_stack->force_eof = TRUE; lex_stack->st_next = _drop_stack_do( lex_stack->st_next); } return retv; } /* Push another file on the parsing stack. If the mode is NULL, create a * FILE_INFO suitable for in-memory parsing; otherwise, create a * FILE_INFO that is bound to a local/disc file. Note that 'path' must * not be NULL, or the function will fail. * * Returns TRUE if a new info record was pushed onto the stack. */ int/*BOOL*/ lex_push_file( const char * path, const char * mode ) { struct FILE_INFO * next = NULL; if (NULL != path) { next = lex_open(path, mode); if (NULL != next) { next->st_next = lex_stack; lex_stack = next; } } return (NULL != next); } /* Pop, close & free the top of the include stack, unless the stack * contains only a singleton input object. In that case the function * fails, because the parser does not expect the input stack to be * empty. * * Returns TRUE if an object was successfuly popped from the stack. */ int/*BOOL*/ lex_pop_file(void) { struct FILE_INFO * head = lex_stack; struct FILE_INFO * tail = NULL; if (NULL != head) { tail = head->st_next; if (NULL != tail) { lex_stack = tail; lex_close(head); } } return (NULL != tail); } /* Get include nesting level. This currently loops over the stack and * counts elements; but since this is of concern only with an include * statement and the nesting depth has a small limit, there's no * bottleneck expected here. * * Returns the nesting level of includes, that is, the current depth of * the lexer input stack. * * Note: */ size_t lex_level(void) { size_t cnt = 0; struct FILE_INFO *ipf = lex_stack; while (NULL != ipf) { cnt++; ipf = ipf->st_next; } return cnt; } /* check if the current input is from a file */ int/*BOOL*/ lex_from_file(void) { return (NULL != lex_stack) && (NULL != lex_stack->fpi); } struct FILE_INFO * lex_current() { /* this became so simple, it could be a macro. But then, * lex_stack needed to be global... */ return lex_stack; } /* STATE MACHINES * -------------- */ /* Keywords */ static int is_keyword( char *lexeme, follby *pfollowedby ) { follby fb; int curr_s; /* current state index */ int token; int i; curr_s = SCANNER_INIT_S; token = 0; for (i = 0; lexeme[i]; i++) { while (curr_s && (lexeme[i] != SS_CH(sst[curr_s]))) curr_s = SS_OTHER_N(sst[curr_s]); if (curr_s && (lexeme[i] == SS_CH(sst[curr_s]))) { if ('\0' == lexeme[i + 1] && FOLLBY_NON_ACCEPTING != SS_FB(sst[curr_s])) { fb = SS_FB(sst[curr_s]); *pfollowedby = fb; token = curr_s; break; } curr_s = SS_MATCH_N(sst[curr_s]); } else break; } return token; } /* Integer */ static int is_integer( char *lexeme ) { int i; int is_neg; u_int u_val; i = 0; /* Allow a leading minus sign */ if (lexeme[i] == '-') { i++; is_neg = TRUE; } else { is_neg = FALSE; } /* Check that all the remaining characters are digits */ for (; lexeme[i] != '\0'; i++) { if (!isdigit((u_char)lexeme[i])) return FALSE; } if (is_neg) return TRUE; /* Reject numbers that fit in unsigned but not in signed int */ if (1 == sscanf(lexeme, "%u", &u_val)) return (u_val <= INT_MAX); else return FALSE; } /* U_int -- assumes is_integer() has returned FALSE */ static int is_u_int( char *lexeme ) { int i; int is_hex; i = 0; if ('0' == lexeme[i] && 'x' == tolower((u_char)lexeme[i + 1])) { i += 2; is_hex = TRUE; } else { is_hex = FALSE; } /* Check that all the remaining characters are digits */ for (; lexeme[i] != '\0'; i++) { if (is_hex && !isxdigit((u_char)lexeme[i])) return FALSE; if (!is_hex && !isdigit((u_char)lexeme[i])) return FALSE; } return TRUE; } /* Double */ static int is_double( char *lexeme ) { u_int num_digits = 0; /* Number of digits read */ u_int i; i = 0; /* Check for an optional '+' or '-' */ if ('+' == lexeme[i] || '-' == lexeme[i]) i++; /* Read the integer part */ for (; lexeme[i] && isdigit((u_char)lexeme[i]); i++) num_digits++; /* Check for the optional decimal point */ if ('.' == lexeme[i]) { i++; /* Check for any digits after the decimal point */ for (; lexeme[i] && isdigit((u_char)lexeme[i]); i++) num_digits++; } /* * The number of digits in both the decimal part and the * fraction part must not be zero at this point */ if (!num_digits) return 0; /* Check if we are done */ if (!lexeme[i]) return 1; /* There is still more input, read the exponent */ if ('e' == tolower((u_char)lexeme[i])) i++; else return 0; /* Read an optional Sign */ if ('+' == lexeme[i] || '-' == lexeme[i]) i++; /* Now read the exponent part */ while (lexeme[i] && isdigit((u_char)lexeme[i])) i++; /* Check if we are done */ if (!lexeme[i]) return 1; else return 0; } /* is_special() - Test whether a character is a token */ static inline int is_special( int ch ) { return strchr(special_chars, ch) != NULL; } static int is_EOC( int ch ) { if ((old_config_style && (ch == '\n')) || (!old_config_style && (ch == ';'))) return 1; return 0; } char * quote_if_needed(char *str) { char *ret; size_t len; size_t octets; len = strlen(str); octets = len + 2 + 1; ret = emalloc(octets); if ('"' != str[0] && (strcspn(str, special_chars) < len || strchr(str, ' ') != NULL)) { snprintf(ret, octets, "\"%s\"", str); } else strlcpy(ret, str, octets); return ret; } static int create_string_token( char *lexeme ) { char *pch; /* * ignore end of line whitespace */ pch = lexeme; while (*pch && isspace((u_char)*pch)) pch++; if (!*pch) { yylval.Integer = T_EOC; return yylval.Integer; } yylval.String = estrdup(lexeme); return T_String; } /* * yylex() - function that does the actual scanning. * Bison expects this function to be called yylex and for it to take no * input and return an int. * Conceptually yylex "returns" yylval as well as the actual return * value representing the token or type. */ int yylex(void) { static follby followedby = FOLLBY_TOKEN; size_t i; int instring; int yylval_was_set; int converted; int token; /* The return value */ int ch; instring = FALSE; yylval_was_set = FALSE; do { /* Ignore whitespace at the beginning */ while (EOF != (ch = lex_getch(lex_stack)) && isspace(ch) && !is_EOC(ch)) ; /* Null Statement */ if (EOF == ch) { if ( ! lex_pop_file()) return 0; token = T_EOC; goto normal_return; } else if (is_EOC(ch)) { /* end FOLLBY_STRINGS_TO_EOC effect */ followedby = FOLLBY_TOKEN; token = T_EOC; goto normal_return; } else if (is_special(ch) && FOLLBY_TOKEN == followedby) { /* special chars are their own token values */ token = ch; /* * '=' outside simulator configuration implies * a single string following as in: * setvar Owner = "The Boss" default */ if ('=' == ch && old_config_style) followedby = FOLLBY_STRING; yytext[0] = (char)ch; yytext[1] = '\0'; goto normal_return; } else lex_ungetch(ch, lex_stack); /* save the position of start of the token */ lex_stack->tokpos = lex_stack->curpos; /* Read in the lexeme */ i = 0; while (EOF != (ch = lex_getch(lex_stack))) { yytext[i] = (char)ch; /* Break on whitespace or a special character */ if (isspace(ch) || is_EOC(ch) || '"' == ch || (FOLLBY_TOKEN == followedby && is_special(ch))) break; /* Read the rest of the line on reading a start of comment character */ if ('#' == ch) { while (EOF != (ch = lex_getch(lex_stack)) && '\n' != ch) ; /* Null Statement */ break; } i++; if (i >= COUNTOF(yytext)) goto lex_too_long; } /* Pick up all of the string inside between " marks, to * end of line. If we make it to EOL without a * terminating " assume it for them. * * XXX - HMS: I'm not sure we want to assume the closing " */ if ('"' == ch) { instring = TRUE; while (EOF != (ch = lex_getch(lex_stack)) && ch != '"' && ch != '\n') { yytext[i++] = (char)ch; if (i >= COUNTOF(yytext)) goto lex_too_long; } /* * yytext[i] will be pushed back as not part of * this lexeme, but any closing quote should * not be pushed back, so we read another char. */ if ('"' == ch) ch = lex_getch(lex_stack); } /* Pushback the last character read that is not a part * of this lexeme. This fails silently if ch is EOF, * but then the EOF condition persists and is handled on * the next turn by the include stack mechanism. */ lex_ungetch(ch, lex_stack); yytext[i] = '\0'; } while (i == 0); /* Now return the desired token */ /* First make sure that the parser is *not* expecting a string * as the next token (based on the previous token that was * returned) and that we haven't read a string. */ if (followedby == FOLLBY_TOKEN && !instring) { token = is_keyword(yytext, &followedby); if (token) { /* * T_Server is exceptional as it forces the * following token to be a string in the * non-simulator parts of the configuration, * but in the simulator configuration section, * "server" is followed by "=" which must be * recognized as a token not a string. */ if (T_Server == token && !old_config_style) followedby = FOLLBY_TOKEN; goto normal_return; } else if (is_integer(yytext)) { yylval_was_set = TRUE; errno = 0; if ((yylval.Integer = strtol(yytext, NULL, 10)) == 0 && ((errno == EINVAL) || (errno == ERANGE))) { msyslog(LOG_ERR, "Integer cannot be represented: %s", yytext); if (lex_from_file()) { exit(1); } else { /* force end of parsing */ yylval.Integer = 0; return 0; } } token = T_Integer; goto normal_return; } else if (is_u_int(yytext)) { yylval_was_set = TRUE; if ('0' == yytext[0] && 'x' == tolower((unsigned long)yytext[1])) converted = sscanf(&yytext[2], "%x", &yylval.U_int); else converted = sscanf(yytext, "%u", &yylval.U_int); if (1 != converted) { msyslog(LOG_ERR, "U_int cannot be represented: %s", yytext); if (lex_from_file()) { exit(1); } else { /* force end of parsing */ yylval.Integer = 0; return 0; } } token = T_U_int; goto normal_return; } else if (is_double(yytext)) { yylval_was_set = TRUE; errno = 0; if ((yylval.Double = atof(yytext)) == 0 && errno == ERANGE) { msyslog(LOG_ERR, "Double too large to represent: %s", yytext); exit(1); } else { token = T_Double; goto normal_return; } } else { /* Default: Everything is a string */ yylval_was_set = TRUE; token = create_string_token(yytext); goto normal_return; } } /* * Either followedby is not FOLLBY_TOKEN or this lexeme is part * of a string. Hence, we need to return T_String. * * _Except_ we might have a -4 or -6 flag on a an association * configuration line (server, peer, pool, etc.). * * This is a terrible hack, but the grammar is ambiguous so we * don't have a choice. [SK] * * The ambiguity is in the keyword scanner, not ntp_parser.y. * We do not require server addresses be quoted in ntp.conf, * complicating the scanner's job. To avoid trying (and * failing) to match an IP address or DNS name to a keyword, * the association keywords use FOLLBY_STRING in the keyword * table, which tells the scanner to force the next token to be * a T_String, so it does not try to match a keyword but rather * expects a string when -4/-6 modifiers to server, peer, etc. * are encountered. * restrict -4 and restrict -6 parsing works correctly without * this hack, as restrict uses FOLLBY_TOKEN. [DH] */ if ('-' == yytext[0]) { if ('4' == yytext[1]) { token = T_Ipv4_flag; goto normal_return; } else if ('6' == yytext[1]) { token = T_Ipv6_flag; goto normal_return; } } if (FOLLBY_STRING == followedby) followedby = FOLLBY_TOKEN; yylval_was_set = TRUE; token = create_string_token(yytext); normal_return: if (T_EOC == token) DPRINTF(4,("\t\n")); else DPRINTF(4, ("yylex: lexeme '%s' -> %s\n", yytext, token_name(token))); if (!yylval_was_set) yylval.Integer = token; return token; lex_too_long: yytext[min(sizeof(yytext) - 1, 50)] = 0; msyslog(LOG_ERR, "configuration item on line %d longer than limit of %lu, began with '%s'", lex_stack->curpos.nline, (u_long)min(sizeof(yytext) - 1, 50), yytext); /* * If we hit the length limit reading the startup configuration * file, abort. */ if (lex_from_file()) exit(sizeof(yytext) - 1); /* * If it's runtime configuration via ntpq :config treat it as * if the configuration text ended before the too-long lexeme, * hostname, or string. */ yylval.Integer = 0; return 0; }