1 /*-
2  * Copyright (c) 2003-2004 Andrey Simonenko
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include "config.h"
28 
29 #ifndef lint
30 static const char rcsid[] ATTR_UNUSED =
31   "@(#)$Id: parser.c,v 1.4 2011/01/23 18:42:35 simon Exp $";
32 #endif /* !lint */
33 
34 /*
35  * I considered using of yacc/lex for implementing the parser for
36  * ipa.conf(5), but have found that there will not be much advantages
37  * in using such tools.  At least: ipa.conf(5) accepts macro variables
38  * and a module is allowed to add macro variables at any time, when it
39  * parses a configuration stream, so we have to get one-by-one logical
40  * line from the file and then parse it or give it to a module for parsing.
41  * If a module considers that yacc/lex or similar tools are better, then
42  * it can mark all its arguments values as IPA_CONF_TYPE_MISC and take
43  * full control on parsing any argument.
44  */
45 
46 #include <sys/types.h>
47 
48 #include <ctype.h>
49 #include <errno.h>
50 #include <limits.h>
51 #include <stdarg.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <strings.h>
56 
57 #include "ipa_mod.h"
58 
59 #include "queue.h"
60 
61 #include "memfunc.h"
62 #include "parser.h"
63 
64 /*
65  * Set DEBUG_PARSER to non-zero value to output debug
66  * information about parsing to stderr.
67  */
68 #ifndef DEBUG_PARSER
69 # define DEBUG_PARSER 0
70 #endif
71 
72 ipa_mem_type	*m_parser;		/* Memory allocated by parser. */
73 char		*parser_str_buf = NULL;	/* -> buffer in parser_stringify(). */
74 
75 void		(*parser_vlogmsgx)(const char *, va_list) = NULL;
76 
77 unsigned int	parser_token_id;	/* Token ID. */
78 char		*parser_token;		/* Token name or NULL (section end). */
79 char		*parser_args;		/* Arguments or NULL (no arguments). */
80 size_t		parser_args_len;	/* strlen() of arguments. */
81 int		parser_nargs;		/* Number of arguments. */
82 
83 #define PB_SIZE		4096		/* Initial size of buffer. */
84 #define PB_SIZE_MAX	(4096 * 1024)	/* Maximum size of buffer. */
85 
86 /* Stack of pbs. */
87 static SLIST_HEAD(EMPTY, parser_pb) pb_stack;
88 
89 /* Pointer to current char read by parser_get_curhcar(). */
90 static unsigned char *curchar;
91 
92 /*
93  * One symbol (macro variable).
94  */
95 struct psym {
96 	TAILQ_ENTRY(psym) link;		/* List of symbols.		*/
97 	char		*sym;		/* Symbol's name.		*/
98 	char		*val;		/* Symbol's value.		*/
99 	size_t		val_len;	/* Length of val.		*/
100 	char		copy_flag;	/* If set then copy sym and val.*/
101 };
102 
103 TAILQ_HEAD(psym_list, psym);		/* Head of list of symbols. */
104 
105 /* Local and global symbols lists. */
106 static struct psym_list	local_sym_list;
107 static struct psym_list	global_sym_list;
108 
109 static int	section_cnt;		/* +1 when '{' and -1 when '}'. */
110 static unsigned int quotes_cnt;		/* Number of '\"' in the argument. */
111 
112 /* Set if we are in a string. */
113 static char	in_string_flag;
114 
115 /* Set if there was a space character in arguments. */
116 static char	was_space_flag;
117 
118 /* Set if we are in symbol definition. */
119 static char	sym_def_flag;
120 
121 static void	logmsgx(const char *, ...) ATTR_FORMAT(printf, 1, 2);
122 static void	syntax_logmsgx(const char *, ...) ATTR_FORMAT(printf, 1, 2);
123 static void	error_logmsgx(const char *, ...) ATTR_FORMAT(printf, 1, 2);
124 
125 #if DEBUG_PARSER
126 static void	debuglog(const char *, ...) ATTR_FORMAT(printf, 1, 2);
127 #endif
128 
129 /*
130  * Some constants for characters.
131  */
132 enum {
133 	CH_NEWLINE = 0,	/*  0 | '\n'		*/
134 	CH_SEMICOLON,	/*  1 | ';'		*/
135 	CH_SPACE,	/*  2 | ' ' or '\t'	*/
136 	CH_QUOTE,	/*  3 | '\"'		*/
137 	CH_OPEN_BRACE,	/*  4 | '{'		*/
138 	CH_CLOSE_BRACE,	/*  5 | '}'		*/
139 	CH_EQUAL,	/*  6 | '='		*/
140 	CH_POUND,	/*  7 | '#'		*/
141 	CH_DOLLAR,	/*  8 | '$'		*/
142 	CH_COLON,	/*  9 | ':'		*/
143 	CH_UNDERSCORE,	/* 10 | '_'		*/
144 	CH_BACKSLASH,	/* 11 | '\\'		*/
145 	CH_SLASH,	/* 12 | '/'		*/
146 	CH_ALPHA,	/* 13 | letter		*/
147 	CH_DIGIT,	/* 14 | digit		*/
148 	CH_PUNCT,	/* 15 | punctuation	*/
149 	CH_OTHER	/* 16 | other		*/
150 };
151 
152 #if DEBUG_PARSER
153 static const char *const ch_msg[] = { "NEWLINE", "SEMICOLON", "SPACE", "QUOTE",
154 	"OPEN_BRACE", "CLOSE_BRACE", "EQUAL", "POUND", "DOLLAR", "COLON",
155 	"UNDERSCORE", "BACKSLASH", "SLASH", "ALPHA", "DIGIT", "PUNCT",
156 	"OTHER" };
157 #endif
158 
159 #define PARSER_AC_T unsigned char	/* Type for AC_xxx values. */
160 #define PARSER_ST_T unsigned char	/* Type for ST_xxx and ER_xxx values. */
161 
162 /*
163  * Parser's states.
164  */
165 enum {
166 	ST_INI = 0,	/*  0 | Initial state. */
167 	ST_TOK,		/*  1 | Characters go to the token. */
168 	ST_ARG,		/*  2 | Characters go to the argument. */
169 	ST_STR,		/*  3 | Characters go to a string in the argument. */
170 	ST_AEX		/*  4 | An argument is expected, wait for non space. */
171 };
172 
173 #define ST_NO 5		/* Number of states. */
174 
175 static PARSER_ST_T curstate = ST_INI; /* Current state. */
176 
177 #if DEBUG_PARSER
178 static const char *const st_msg[] = { "INI", "TOK", "ARG", "STR", "AEX" };
179 #endif
180 
181 /*
182  * Parser's actions.
183  */
184 enum {
185 	AC_SKP = 0,	/*  0 | Skip a character, continue parsing. */
186 	AC_AAR,		/*  1 | Add a character to the argument. */
187 	AC_ATK,		/*  2 | Add a character to the token. */
188 	AC_AQU,		/*  3 | Add '\"' to the argument. */
189 	AC_TOK,		/*  4 | Definition of the token is complete. */
190 	AC_PAR,		/*  5 | Definition of the parameter is complete. */
191 	AC_BOS,		/*  6 | Begin of the section. */
192 	AC_EOS,		/*  7 | End of the section. */
193 	AC_PND,		/*  8 | '#' appeared. */
194 	AC_FMT,		/*  9 | '\\' appeared in a string. */
195 	AC_ASP,		/* 10 | Space in the argument. */
196 	AC_SLH,		/* 11 | '/' appeared. */
197 	AC_DLR,		/* 12 | '$' appeared. */
198 	AC_ERR		/* 13 | A syntax error occurred. */
199 };
200 
201 #define AC_NO 14	/* Number of actions. */
202 
203 #if DEBUG_PARSER
204 static const char *const ac_msg[] = { "SKP", "AAR", "ATK", "AQU", "TOK",
205 	"PAR", "BOS", "EOS", "PND", "FMT", "ASP", "SLH", "DLR", "ERR" };
206 #endif
207 
208 /*
209  * Parser's errors.
210  */
211 enum {
212 	ER_UXP,		/*  0 | Unexpected character. */
213 	ER_NSN,		/*  1 | No section name. */
214 	ER_NLV,		/*  2 | No lvalue: section/parameter name is absent). */
215 	ER_FMT		/*  3 | Wrong character after '\\' in a string. */
216 };
217 
218 #if DEBUG_PARSER
219 static const char *const er_msg[] = { "UXP", "NSN", "NLV", "FMT" };
220 #endif
221 
222 static const char *const error_msg[] = {
223 /*  0 */ "unexpected character",
224 /*  1 */ "no section name",
225 /*  2 */ "no lvalue (section or parameter name is absent)",
226 /*  3 */ "wrong format character after '\\' in a string"
227 };
228 
229 struct stac {
230 	PARSER_AC_T	action;		/* AC_xxx */
231 	PARSER_ST_T	state;		/* ST_xxx or
232 					   ER_xxx if action == AC_ERR. */
233 } ATTR_PACKED;
234 
235 static const struct stac *curstac;	/* Current entry in stac_tbl. */
236 
237 static const struct stac stac_tbl[][ST_NO] = {
238 /*	         ST_INI           ST_TOK           ST_ARG           ST_STR           ST_AEX     */
239 /*  \n */ { {AC_SKP,ST_INI}, {AC_TOK,ST_AEX}, {AC_ASP,ST_ARG}, {AC_AAR,ST_STR}, {AC_SKP,ST_AEX} },
240 /*  ;  */ { {AC_ERR,ER_UXP}, {AC_PAR,ST_INI}, {AC_PAR,ST_INI}, {AC_AAR,ST_STR}, {AC_PAR,ST_INI} },
241 /*  sp */ { {AC_SKP,ST_INI}, {AC_TOK,ST_AEX}, {AC_ASP,ST_ARG}, {AC_AAR,ST_STR}, {AC_SKP,ST_AEX} },
242 /*  \" */ { {AC_ERR,ER_UXP}, {AC_ERR,ER_UXP}, {AC_AQU,ST_STR}, {AC_AQU,ST_ARG}, {AC_AQU,ST_STR} },
243 /*  {  */ { {AC_ERR,ER_NSN}, {AC_BOS,ST_INI}, {AC_BOS,ST_INI}, {AC_AAR,ST_STR}, {AC_BOS,ST_INI} },
244 /*  }  */ { {AC_EOS,ST_INI}, {AC_ERR,ER_UXP}, {AC_ERR,ER_UXP}, {AC_AAR,ST_STR}, {AC_ERR,ER_UXP} },
245 /*  =  */ { {AC_ERR,ER_NLV}, {AC_TOK,ST_ARG}, {AC_AAR,ST_ARG}, {AC_AAR,ST_STR}, {AC_SKP,ST_ARG} },
246 /*  #  */ { {AC_PND,ST_INI}, {AC_PND,ST_ARG}, {AC_PND,ST_ARG}, {AC_AAR,ST_STR}, {AC_PND,ST_AEX} },
247 /*  $  */ { {AC_DLR,ST_AEX}, {AC_DLR,ST_TOK}, {AC_DLR,ST_ARG}, {AC_DLR,ST_STR}, {AC_DLR,ST_AEX} },
248 /*  :  */ { {AC_ATK,ST_TOK}, {AC_ATK,ST_TOK}, {AC_AAR,ST_ARG}, {AC_AAR,ST_STR}, {AC_AAR,ST_ARG} },
249 /*  _  */ { {AC_ATK,ST_TOK}, {AC_ATK,ST_TOK}, {AC_AAR,ST_ARG}, {AC_AAR,ST_STR}, {AC_AAR,ST_ARG} },
250 /* \\  */ { {AC_ERR,ER_UXP}, {AC_ERR,ER_UXP}, {AC_AAR,ST_ARG}, {AC_FMT,ST_STR}, {AC_AAR,ST_ARG} },
251 /*  /  */ { {AC_SLH,ST_INI}, {AC_SLH,ST_TOK}, {AC_SLH,ST_ARG}, {AC_AAR,ST_STR}, {AC_SLH,ST_AEX} },
252 /*alpha*/ { {AC_ATK,ST_TOK}, {AC_ATK,ST_TOK}, {AC_AAR,ST_ARG}, {AC_AAR,ST_STR}, {AC_AAR,ST_ARG} },
253 /*digit*/ { {AC_ERR,ER_UXP}, {AC_ATK,ST_TOK}, {AC_AAR,ST_ARG}, {AC_AAR,ST_STR}, {AC_AAR,ST_ARG} },
254 /*punct*/ { {AC_ERR,ER_UXP}, {AC_ERR,ER_UXP}, {AC_AAR,ST_ARG}, {AC_AAR,ST_STR}, {AC_AAR,ST_ARG} },
255 /*other*/ { {AC_ERR,ER_UXP}, {AC_ERR,ER_UXP}, {AC_ERR,ER_UXP}, {AC_AAR,ST_STR}, {AC_ERR,ER_UXP} }
256 };
257 
258 static unsigned char *whatis_char;	/* Characters --> classes table. */
259 
260 /*
261  * This generic configuration buffer structure is used for adding
262  * characters to token, argument and symbol name.
263  */
264 struct conf_buf {
265 	char		*buf;		/* Buffer. */
266 	size_t		size;		/* Its size. */
267 	size_t		off;		/* Current offset. */
268 };
269 
270 static struct conf_buf token_buf;	/* Buffer for a token. */
271 static struct conf_buf args_buf;	/* Buffer for arguments. */
272 static struct conf_buf sym_buf;		/* Buffer for a symbol name. */
273 static struct conf_buf *cur_buf;	/* Pointer to the current buffer. */
274 
275 /*
276  * Return codes from parser_get_curchar() and ac_xxx().
277  */
278 enum {
279 	AC_RET_CONT = 0,	/*  0 | Update state, continue parsing. */
280 	AC_RET_LINE,		/*  1 | Update state, return logical line. */
281 	AC_RET_CONTX,		/*  2 | Continue parsing. */
282 	AC_RET_ERR,		/*  3 | An error occurred. */
283 	AC_RET_EOF		/*  4 | EOF. */
284 };
285 
286 static int	ac_skp(void), ac_aar(void), ac_atk(void), ac_aqu(void);
287 static int	ac_tok(void), ac_par(void), ac_bos(void), ac_eos(void);
288 static int	ac_pnd(void), ac_fmt(void), ac_asp(void), ac_slh(void);
289 static int	ac_dlr(void), ac_err(void);
290 
291 static int (* const ac_func_tbl[AC_NO])(void) = {
292 	ac_skp,			/* AC_SKP */
293 	ac_aar,			/* AC_AAR */
294 	ac_atk,			/* AC_ATK */
295 	ac_aqu,			/* AC_AQU */
296 	ac_tok,			/* AC_TOK */
297 	ac_par,			/* AC_PAR */
298 	ac_bos,			/* AC_BOS */
299 	ac_eos,			/* AC_EOS */
300 	ac_pnd,			/* AC_PND */
301 	ac_fmt,			/* AC_FMT */
302 	ac_asp,			/* AC_ASP */
303 	ac_slh,			/* AC_SLH */
304 	ac_dlr,			/* AC_DLR */
305 	ac_err			/* AC_ERR */
306 };
307 
308 /*
309  * The wrapper for parser_vlog, its declaration above will check
310  * format string and arguments.
311  */
312 static void
logmsgx(const char * format,...)313 logmsgx(const char *format, ...)
314 {
315 	va_list ap;
316 
317 	va_start(ap, format);
318 	parser_vlogmsgx(format, ap);
319 	va_end(ap);
320 }
321 
322 #if DEBUG_PARSER
323 /*
324  * printf-like function, which outputs debug messages to stderr.
325  */
326 static void
debuglog(const char * format,...)327 debuglog(const char *format, ...)
328 {
329 	va_list ap;
330 
331 	va_start(ap, format);
332 	vfprintf(stderr, format, ap);
333 	va_end(ap);
334 }
335 #endif
336 
337 /*
338  * Output error message header.
339  */
340 static void
error_header_log(const char * what)341 error_header_log(const char *what)
342 {
343 	const struct parser_pb *pb;
344 
345 	pb = SLIST_FIRST(&pb_stack);
346 	if (pb != NULL) {
347 		char buf[5];
348 
349 		if (isprint(*curchar)) {
350 			buf[0] = *curchar;
351 			buf[1] = '\0';
352 		} else
353 			switch (*curchar) {
354 			case '\n':
355 				strncpy(buf, "\\n", sizeof(buf));
356 				break;
357 			case '\t':
358 				strncpy(buf, "\\t", sizeof(buf));
359 				break;
360 			default:
361 				snprintf(buf, sizeof(buf), "0x%02x", *curchar);
362 			}
363 		if (pb->fp != NULL)
364 			logmsgx("%serror occurred near line %u at character "
365 			    "'%s' in %s", what, pb->lineno, buf, pb->fname);
366 		else
367 			logmsgx("%serror occurred near line %u at character "
368 			    "'%s' in %s (in macro variable ${%s} value)",
369 			    what, pb->lineno, buf, pb->fname, pb->sname);
370 	} else
371 		logmsgx("%serror occurred near last line in main "
372 		    "configuration file", what);
373 }
374 
375 /*
376  * Report about syntax error.
377  */
378 static void
syntax_logmsgx(const char * format,...)379 syntax_logmsgx(const char *format, ...)
380 {
381 	va_list ap;
382 
383 	error_header_log("syntax ");
384 	va_start(ap, format);
385 	parser_vlogmsgx(format, ap);
386 	va_end(ap);
387 }
388 
389 /*
390  * Report about syntax error given by the code.
391  */
392 static void
syntax_error(PARSER_ST_T error)393 syntax_error(PARSER_ST_T error)
394 {
395 	syntax_logmsgx("%s", error_msg[error]);
396 }
397 
398 /*
399  * Report about non-syntax error.
400  */
401 static void
error_logmsgx(const char * format,...)402 error_logmsgx(const char *format, ...)
403 {
404 	va_list ap;
405 
406 	error_header_log("");
407 	va_start(ap, format);
408 	parser_vlogmsgx(format, ap);
409 	va_end(ap);
410 }
411 
412 /*
413  * Return a pointer to psym for the symbol with name sym
414  * in the given psym_list.
415  */
416 static struct psym *
find_sym(const struct psym_list * list,const char * sym)417 find_sym(const struct psym_list *list, const char *sym)
418 {
419 	struct psym *psym;
420 
421 	TAILQ_FOREACH(psym, list, link)
422 		if (strcmp(psym->sym, sym) == 0)
423 			return (psym);
424 	return (NULL);
425 }
426 
427 /*
428  * Release memory used by the given psym_list.
429  */
430 static void
free_sym_list(struct psym_list * list)431 free_sym_list(struct psym_list *list)
432 {
433 	struct psym *psym, *psym_next;
434 
435 	TAILQ_FOREACH_SAFE(psym, list, link, psym_next) {
436 		if (psym->copy_flag) {
437 			mem_free(psym->sym, m_parser);
438 			mem_free(psym->val, m_parser);
439 		}
440 		mem_free(psym, m_parser);
441 	}
442 	TAILQ_INIT(list);
443 }
444 
445 /*
446  * Add a new symbol with name sym and with value val to list, if
447  * the list already has this symbol, then replace it with a new value,
448  * honoring the copy_flag value.
449  */
450 static int
sym_add(struct psym_list * list,char * sym,char * val,int copy_flag)451 sym_add(struct psym_list *list, char *sym, char *val, int copy_flag)
452 {
453 	struct psym *psym;
454 	char old_copy_flag;
455 
456 	psym = find_sym(list, sym);
457 	if (psym != NULL) {
458 		/* Replace symbol. */
459 		old_copy_flag = psym->copy_flag;
460 		if (old_copy_flag) {
461 			if (!copy_flag)
462 				mem_free(psym->sym, m_parser);
463 			mem_free(psym->val, m_parser);
464 		}
465 	} else {
466 		/* New symbol. */
467 		old_copy_flag = 0;
468 		psym = mem_malloc(sizeof(*psym), m_parser);
469 		if (psym == NULL) {
470 			logmsgx("sym_add: mem_malloc failed");
471 			return (-1);
472 		}
473 		TAILQ_INSERT_TAIL(list, psym, link);
474 	}
475 
476 	if (copy_flag) {
477 		if (!old_copy_flag) {
478 			psym->sym = mem_strdup(sym, m_parser);
479 			if (psym->sym == NULL) {
480 				logmsgx("sym_add: mem_strdup failed");
481 				mem_free(psym, m_parser);
482 				return (-1);
483 			}
484 		}
485 		psym->val = mem_strdup(val, m_parser);
486 		if (psym->val == NULL) {
487 			logmsgx("sym_add: mem_strdup failed");
488 			mem_free(psym->sym, m_parser);
489 			mem_free(psym, m_parser);
490 			return (-1);
491 		}
492 	} else {
493 		psym->sym = sym;
494 		psym->val = val;
495 	}
496 
497 	psym->val_len = strlen(val);
498 	psym->copy_flag = copy_flag;
499 
500 	return (0);
501 }
502 
503 /*
504  * Remove a symbol sym from the list, return -1 if the list
505  * does not have a symbol with the given name.
506  */
507 static int
sym_del(struct psym_list * list,const char * sym)508 sym_del(struct psym_list *list, const char *sym)
509 {
510 	struct psym *psym;
511 
512 	psym = find_sym(list, sym);
513 	if (psym != NULL) {
514 		TAILQ_REMOVE(list, psym, link);
515 		if (psym->copy_flag) {
516 			mem_free(psym->sym, m_parser);
517 			mem_free(psym->val, m_parser);
518 		}
519 		mem_free(psym, m_parser);
520 	} else
521 		return (-1);
522 	return (0);
523 }
524 
525 /*
526  * Delete a local symbol.
527  */
528 int
parser_local_sym_del(const char * sym)529 parser_local_sym_del(const char *sym)
530 {
531 	return (sym_del(&local_sym_list, sym));
532 }
533 
534 /*
535  * Add a local symbol.
536  */
537 int
parser_local_sym_add(char * sym,char * val,int copy_flag)538 parser_local_sym_add(char *sym, char *val, int copy_flag)
539 {
540 	return (sym_add(&local_sym_list, sym, val, copy_flag));
541 }
542 
543 /*
544  * Delete a global symbol.
545  */
546 int
parser_global_sym_del(const char * sym)547 parser_global_sym_del(const char *sym)
548 {
549 	return (sym_del(&global_sym_list, sym));
550 }
551 
552 /*
553  * Add a global symbol.
554  */
555 int
parser_global_sym_add(char * sym,char * val,int copy_flag)556 parser_global_sym_add(char *sym, char *val, int copy_flag)
557 {
558 	return (sym_add(&global_sym_list, sym, val, copy_flag));
559 }
560 
561 /*
562  * Create a new pb.  If size is zero, then allocate pb->buf,
563  * else do not allocate pb->buf, it should be allocated somewhere
564  * outside of this function.
565  */
566 struct parser_pb *
parser_new_pb(size_t size)567 parser_new_pb(size_t size)
568 {
569 	struct parser_pb *pb;
570 
571 	pb = mem_malloc(sizeof(*pb), m_parser);
572 	if (pb == NULL) {
573 		logmsgx("parser_new_pb: mem_malloc failed");
574 		return (NULL);
575 	}
576 	if (size == 0) {
577 		pb->buf_size = PB_SIZE;
578 		pb->buf = mem_malloc(PB_SIZE, m_parser);
579 		if (pb->buf == NULL) {
580 			logmsgx("parser_new_pb: mem_malloc failed");
581 			mem_free(pb, m_parser);
582 			return (NULL);
583 		}
584 	} else {
585 		pb->buf_size = size;
586 		pb->buf = NULL;
587 	}
588 	pb->buf_nread = pb->buf_off = 0;
589 	pb->fname = NULL;
590 	pb->sname = NULL;
591 	pb->fp = NULL;
592 	pb->lineno = 1;
593 	return (pb);
594 }
595 
596 /*
597  * Push pb to the top of pb_stack and set curchar pointer.  If the
598  * given pb is a macro variable's value, then check for loops.
599  */
600 int
parser_push_pb(struct parser_pb * pb1)601 parser_push_pb(struct parser_pb *pb1)
602 {
603 	const struct parser_pb *pb;
604 	struct parser_pb *pb2;
605 
606 	pb = SLIST_FIRST(&pb_stack);
607 
608 #if DEBUG_PARSER
609 	if (pb != NULL) {
610 		debuglog("CURPBUF:   ");
611 		debuglog("file %s", pb->fname);
612 		if (pb->sname != NULL)
613 			debuglog(" (sym ${%s} mem <%s>)", pb->sname, pb->buf);
614 		debuglog(" size %lu, off %lu, nread %lu\n",
615 		    (unsigned long)pb->buf_size, (unsigned long)pb->buf_off,
616 		    (unsigned long)pb->buf_nread);
617 	}
618 	debuglog("PBUF PUSH: ");
619 	if (pb1->fname != NULL)
620 		debuglog("file %s", pb1->fname);
621 	else
622 		debuglog("sym ${%s} mem <%s>", pb1->sname, pb1->buf);
623 	debuglog(" size %lu, off %lu, nread %lu\n",
624 	    (unsigned long)pb1->buf_size, (unsigned long)pb1->buf_off,
625 	    (unsigned long)pb1->buf_nread);
626 #endif /* DEBUG_PARSER */
627 
628 	/* Check for loopbacks in macro variable expanding. */
629 	if (pb1->sname != NULL)
630 		SLIST_FOREACH(pb2, &pb_stack, link)
631 			if (pb2->sname != NULL)
632 				if (strcmp(pb2->sname, pb1->sname) == 0) {
633 					error_logmsgx("parser_push_pb: loop"
634 					    "back is detected while expanding "
635 					    "${%s} macro variable", pb1->sname);
636 					return (-1);
637 				}
638 
639 	/* Link just created new pb. */
640 	SLIST_INSERT_HEAD(&pb_stack, pb1, link);
641 
642 	if (pb1->fname == NULL) {
643 		/* This is needed only for log functions. */
644 		pb1->fname = pb->fname;
645 		pb1->lineno = pb->lineno;
646 	}
647 
648 	/* ...-1 for parser_get_curchar(). */
649 	curchar = pb1->buf - 1;
650 
651 	return (0);
652 }
653 
654 /*
655  * Pop pb from the top of pb_stack and restore curchar pointer.
656  */
657 struct parser_pb *
parser_pop_pb(void)658 parser_pop_pb(void)
659 {
660 	struct parser_pb *pb;
661 
662 	/* Unlink pb. */
663 	pb = SLIST_FIRST(&pb_stack);
664 	SLIST_REMOVE_HEAD(&pb_stack, link);
665 
666 	/* For macro variables buffer is shared with macro variable's value. */
667 	if (pb->fp != NULL)
668 		mem_free(pb->buf, m_parser);
669 
670 	/* Release memory used by pb structure. */
671 	mem_free(pb, m_parser);
672 
673 	/* Get new pb and set curchar, ...-1 for parser_get_curchar(). */
674 	pb = SLIST_FIRST(&pb_stack);
675 	if (pb != NULL)
676 		curchar = pb->buf + pb->buf_off - 1;
677 
678 #if DEBUG_PARSER
679 	if (pb == NULL)
680 		debuglog("PBUF POP:  NULL\n");
681 	else {
682 		debuglog("PBUF POP:  ");
683 		debuglog("file %s", pb->fname);
684 		if (pb->sname != NULL)
685 			debuglog(" (sym ${%s} mem <%s>)", pb->sname, pb->buf);
686 		debuglog(" %lu bytes, off %lu, nread %lu\n",
687 		    (unsigned long)pb->buf_size, (unsigned long)pb->buf_off,
688 		    (unsigned long)pb->buf_nread);
689 	}
690 #endif
691 
692 	return (pb);
693 }
694 
695 /*
696  * Return pointer to top pb.
697  */
698 struct parser_pb *
parser_top_pb(void)699 parser_top_pb(void)
700 {
701 	return (SLIST_FIRST(&pb_stack));
702 }
703 
704 /*
705  * Initialize whatis_char table (only once), {token,args,sym}_buf,
706  * symbol tables and pb_stack.  This function can be called,
707  * even if parser_read_string() returned an error.
708  */
709 int
parser_init(void)710 parser_init(void)
711 {
712 	static char called_before = 0;
713 
714 	if (parser_vlogmsgx == NULL) {
715 		/* Cannot even log about problem. */
716 		return (-1);
717 	}
718 
719 	/*
720 	 * Initialize whatis_char only once.  Cannot use static table,
721 	 * because we should honor locale (isxxxx() functions).
722 	 */
723 	if (!called_before) {
724 		unsigned int ch;
725 
726 		called_before = 1;
727 		whatis_char = malloc(2 << (CHAR_BIT - 1));
728 		if (whatis_char == NULL) {
729 			logmsgx("parser_init: malloc: %s", strerror(errno));
730 			return (-1);
731 		}
732 		for (ch = 0; ch < (2 << (CHAR_BIT - 1)); ++ch)
733 			if (isalpha(ch))
734 				whatis_char[ch] = CH_ALPHA;
735 			else if (ispunct(ch))
736 				whatis_char[ch] = CH_PUNCT;
737 			else if (isdigit(ch))
738 				whatis_char[ch] = CH_DIGIT;
739 			else
740 				whatis_char[ch] = CH_OTHER;
741 		whatis_char['\t'] = CH_SPACE;
742 		whatis_char['\n'] = CH_NEWLINE;
743 		whatis_char[' '] = CH_SPACE;
744 		whatis_char['\"'] = CH_QUOTE;
745 		whatis_char['#'] = CH_POUND;
746 		whatis_char['$'] = CH_DOLLAR;
747 		whatis_char['/'] = CH_SLASH;
748 		whatis_char[':'] = CH_COLON;
749 		whatis_char[';'] = CH_SEMICOLON;
750 		whatis_char['='] = CH_EQUAL;
751 		whatis_char['\\'] = CH_BACKSLASH;
752 		whatis_char['_'] = CH_UNDERSCORE;
753 		whatis_char['{'] = CH_OPEN_BRACE;
754 		whatis_char['}'] = CH_CLOSE_BRACE;
755 	}
756 
757 	/* Initialize token_buf, args_buf and sym_buf. */
758 	token_buf.buf = args_buf.buf = sym_buf.buf = NULL;
759 	token_buf.size = args_buf.size = sym_buf.size = 0;
760 
761 	/* Initialize symbol tables. */
762 	TAILQ_INIT(&global_sym_list);
763 	TAILQ_INIT(&local_sym_list);
764 
765 	/* Initialize pb_stack. */
766 	SLIST_INIT(&pb_stack);
767 
768 	section_cnt = 0;
769 
770 	in_string_flag = was_space_flag = sym_def_flag = 0;
771 
772 	return (0);
773 }
774 
775 /*
776  * Do last check of syntax and release all memory except whatis_char
777  * table allocated in parser_init().
778  */
779 int
parser_deinit(void)780 parser_deinit(void)
781 {
782 	/* Last check of syntax. */
783 	if (section_cnt) {
784 		syntax_logmsgx("section is not closed");
785 		return (-1);
786 	}
787 
788 	/* Release internal memory. */
789 	mem_free(token_buf.buf, m_parser);
790 	mem_free(args_buf.buf, m_parser);
791 	mem_free(sym_buf.buf, m_parser);
792 
793 	/*
794 	 * Deinitialize global symbol table, local one automatically
795 	 * was deinitialized.
796 	 */
797 	free_sym_list(&global_sym_list);
798 
799 	return (0);
800 }
801 
802 /*
803  * Add the given character to the current buffer, if there is not
804  * enough space in the buffer, then realloc() it.
805  */
806 static int
parser_add_char(unsigned char ch)807 parser_add_char(unsigned char ch)
808 {
809 	char *buf;
810 
811 	if (cur_buf->off == cur_buf->size) {
812 		if (cur_buf->size == 0)
813 			cur_buf->size = PB_SIZE;
814 		else {
815 			cur_buf->size *= 2;
816 			if (cur_buf->size > PB_SIZE_MAX) {
817 				error_logmsgx("parser_add_char: "
818 				    "too big buffer size %llu",
819 				    (unsigned long long)cur_buf->size);
820 				return (-1);
821 			}
822 		}
823 		buf = mem_realloc(cur_buf->buf, cur_buf->size, m_parser);
824 		if (buf == NULL) {
825 			error_logmsgx("parser_add_char: mem_realloc failed");
826 			return (-1);
827 		}
828 		cur_buf->buf = buf;
829 	}
830 	cur_buf->buf[cur_buf->off++] = ch;
831 	return (0);
832 }
833 
834 /*
835  * Get next curchar from the configuration stream.
836  * Return:
837  *  AC_RET_CONT, if curchar was successfully read;
838  *  AC_RET_ERR, if an error occurred;
839  *  AC_RET_EOF, if EOF occurred.
840  */
841 static int
parser_get_curchar(void)842 parser_get_curchar(void)
843 {
844 	struct parser_pb *pb;
845 
846 	pb = SLIST_FIRST(&pb_stack);
847 	for (;;) {
848 		if (pb->buf_off < pb->buf_nread) {
849 			/* Get curchar from the buffer. */
850 			curchar++;
851 			if (*curchar == '\n' && pb->fp != NULL)
852 				pb->lineno++;
853 			pb->buf_off++;
854 			return (AC_RET_CONT);
855 		}
856 		if (pb->fp != NULL) {
857 			/* Pb content from the file. */
858 			pb->buf_nread = fread(pb->buf, sizeof(char),
859 			    pb->buf_size, pb->fp);
860 			if (pb->buf_nread == 0) {
861 				if (feof(pb->fp) != 0)
862 					return (AC_RET_EOF);
863 				error_logmsgx("parser_get_curchar: fread(%s): "
864 				    "%s", pb->fname, strerror(errno));
865 				return (AC_RET_ERR);
866 			}
867 			pb->buf_off = 0;
868 			curchar = pb->buf - 1;
869 		} else {
870 			/* This pb ended, use previous one. */
871 			pb = parser_pop_pb();
872 		}
873 	}
874 	/* NOTREACHED */
875 }
876 
877 /*
878  * Put curchar back to the buffer, this function must be called
879  * no more than one time after parser_get_curchar(), because it
880  * cannot restore pb_stack.
881  */
882 static int
parser_put_curchar(void)883 parser_put_curchar(void)
884 {
885 	struct parser_pb *pb;
886 
887 	pb = SLIST_FIRST(&pb_stack);
888 	if (pb->buf_off == 0) {
889 		error_logmsgx("internal error: parser_put_curchar: "
890 		    "buf_off == 0");
891 		return (-1);
892 	}
893 
894 	/* Put curchar to the buffer. */
895 	if (*curchar == '\n' && pb->fp != NULL)
896 		pb->lineno--;
897 	curchar--;
898 	pb->buf_off--;
899 
900 	return (0);
901 }
902 
903 #if DEBUG_PARSER
904 /*
905  * Output current character, current state and new state and action.
906  */
907 static void
print_curchar_and_stac(void)908 print_curchar_and_stac(void)
909 {
910 	switch (*curchar) {
911 	case '\n':
912 		debuglog("\\n");
913 		break;
914 	case '\t':
915 		debuglog("\\t");
916 		break;
917 	case ' ':
918 		debuglog("sp");
919 		break;
920 	default:
921 		if (isprint(*curchar))
922 			debuglog(" %c", *curchar);
923 		else
924 			debuglog("%02x", *curchar);
925 	}
926 	debuglog(" : %s -> %s : %s (%s)\n", st_msg[curstate],
927 	    curstac->action != AC_ERR ?
928 	    st_msg[curstac->state] : er_msg[curstac->state],
929 	    ac_msg[curstac->action], ch_msg[whatis_char[*curchar]]);
930 }
931 
932 /*
933  * Output current token and argument.
934  */
935 static void
print_token_and_args(void)936 print_token_and_args(void)
937 {
938 	switch (parser_token_id) {
939 	case TOKEN_ID_SECTION_BEGIN:
940 		debuglog("SECTION_BEGIN: TOKEN <%s>", parser_token);
941 		break;
942 	case TOKEN_ID_SECTION_END:
943 		debuglog("SECTION_END");
944 		break;
945 	case TOKEN_ID_PARAMETER:
946 		debuglog("PARAMETER: TOKEN <%s>", parser_token);
947 		break;
948 	default:
949 		debuglog("Unknown token ID %u", parser_token_id);
950 	}
951 	if (parser_args != NULL)
952 		debuglog(", ARGS <%s>", parser_args);
953 	debuglog("\n");
954 }
955 #endif /* DEBUG_PARSER */
956 
957 static void
init_read_string(void)958 init_read_string(void)
959 {
960 	cur_buf = &token_buf;
961 	parser_args = NULL;
962 	parser_nargs = 0;
963 	token_buf.off = args_buf.off = 0;
964 	was_space_flag = sym_def_flag = 0;
965 	quotes_cnt = 0;
966 }
967 
968 /*
969  * Read one logical string from the configuration stream.
970  * Macro variables definitions are not returned, instead they
971  * are saved in local symbols tables.
972  * Return:
973  *  1, if one string was successfully read;
974  * -1, if syntax or another error occurred;
975  *  0, if EOF occurred.
976  */
977 int
parser_read_string(void)978 parser_read_string(void)
979 {
980 	init_read_string();
981 
982 	for (;;) {
983 		switch (parser_get_curchar()) {
984 		case AC_RET_CONT:
985 			curstac = &stac_tbl[whatis_char[*curchar]][curstate];
986 #if DEBUG_PARSER
987 			print_curchar_and_stac();
988 #endif
989 			switch (ac_func_tbl[curstac->action]()) {
990 			case AC_RET_CONT:
991 				curstate = curstac->state;
992 				break;
993 			case AC_RET_LINE:
994 #if DEBUG_PARSER
995 				print_token_and_args();
996 #endif
997 				curstate = curstac->state;
998 				return (1);
999 			case AC_RET_CONTX:
1000 				break;
1001 			case AC_RET_ERR:
1002 				return (-1);
1003 			case AC_RET_EOF:
1004 				goto end_of_file;
1005 			}
1006 			break;
1007 		case AC_RET_EOF:
1008 			goto end_of_file;
1009 		case AC_RET_ERR:
1010 			return (-1);
1011 		}
1012 	}
1013 
1014 end_of_file:
1015 	if (curstate != ST_INI) {
1016 		if (in_string_flag)
1017 			syntax_logmsgx("string is not closed with '\\\"' "
1018 			    "and end of file occurred");
1019 		else
1020 			syntax_logmsgx("unexpected end of file");
1021 		return (-1);
1022 	}
1023 	return (0);
1024 }
1025 
1026 /*
1027  * Skip current character, continue.
1028  */
1029 static int
ac_skp(void)1030 ac_skp(void)
1031 {
1032 	return (AC_RET_CONT);
1033 }
1034 
1035 /*
1036  * Add curchar to the arguments buffer.
1037  */
1038 static int
ac_aar(void)1039 ac_aar(void)
1040 {
1041 	if (parser_add_char(*curchar) < 0)
1042 		return (AC_RET_ERR);
1043 
1044 	if (was_space_flag) {
1045 		was_space_flag = 0;
1046 		parser_nargs++;
1047 	}
1048 
1049 	return (AC_RET_CONT);
1050 }
1051 
1052 /*
1053  * Add curchar to the token buffer.
1054  */
1055 static int
ac_atk(void)1056 ac_atk(void)
1057 {
1058 	if (parser_add_char(*curchar) < 0)
1059 		return (AC_RET_ERR);
1060 
1061 	return (AC_RET_CONT);
1062 }
1063 
1064 /*
1065  * Add curchar '\"' to the arguments buffer, increase number
1066  * of double quotes and change in_string_flag.
1067  */
1068 static int
ac_aqu(void)1069 ac_aqu(void)
1070 {
1071 	int rv;
1072 
1073 	rv = ac_aar();
1074 	if (rv != AC_RET_CONT)
1075 		return (rv);
1076 
1077 	++quotes_cnt;
1078 	in_string_flag = !in_string_flag;
1079 
1080 	return (AC_RET_CONT);
1081 }
1082 
1083 /*
1084  * Definition of the token is complete.  Switch current conf_buf
1085  * to the arguments buffer.
1086  */
1087 static int
ac_tok(void)1088 ac_tok(void)
1089 {
1090 	if (parser_add_char('\0') < 0)
1091 		return (AC_RET_ERR);
1092 
1093 	cur_buf = &args_buf;
1094 
1095 	return (AC_RET_CONT);
1096 }
1097 
1098 /*
1099  * If configuration buffers are not complete (there is not '\0'
1100  * character at the end for example), then this function must be called.
1101  * Set parser_token and parser_args pointers.
1102  */
1103 static int
complete_conf_bufs(void)1104 complete_conf_bufs(void)
1105 {
1106 	if (cur_buf == &token_buf) {
1107 		if (parser_add_char('\0') < 0)
1108 			return (-1);
1109 	} else if (args_buf.off != 0) {
1110 		char *ptr;
1111 
1112 		ptr = args_buf.buf + args_buf.off - 1;
1113 		switch (*ptr) {
1114 		case ' ':
1115 		case '\t':
1116 			*ptr = '\0';
1117 			break;
1118 		default:
1119 			if (parser_add_char('\0') < 0)
1120 				return (-1);
1121 		}
1122 		parser_args = args_buf.buf;
1123 		parser_args_len = args_buf.off - 1;
1124 		parser_nargs++;
1125 	}
1126 	parser_token = token_buf.buf;
1127 	return (0);
1128 }
1129 
1130 /*
1131  * Definition of the parameter or macro variable is complete.
1132  */
1133 static int
ac_par(void)1134 ac_par(void)
1135 {
1136 	if (complete_conf_bufs() < 0)
1137 		return (AC_RET_ERR);
1138 
1139 	if (sym_def_flag) {
1140 		/* Definition of a macro variable. */
1141 		char *val;
1142 
1143 		if (!parser_arg_is_str()) {
1144 			syntax_logmsgx("macro variable's value should be a "
1145 			    "string");
1146 			return (AC_RET_ERR);
1147 		}
1148 		val = parser_strdup(args_buf.buf, m_parser);
1149 		if (val == NULL) {
1150 			error_logmsgx("cannot allocate memory for value of "
1151 			    "${%s} macro variable", sym_buf.buf);
1152 			return (AC_RET_ERR);
1153 		}
1154 		if (section_cnt == 0) {
1155 			if (parser_global_sym_add(sym_buf.buf, val, 1) < 0) {
1156 				error_logmsgx("cannot register "
1157 				    "a macro variable");
1158 				return (-1);
1159 			}
1160 		} else {
1161 			if (parser_local_sym_add(sym_buf.buf, val, 1) < 0) {
1162 				error_logmsgx("cannot register "
1163 				    "a macro variable");
1164 				return (-1);
1165 			}
1166 		}
1167 		mem_free(val, m_parser);
1168 		init_read_string();
1169 		curstate = ST_INI;
1170 		return (AC_RET_CONTX);
1171 	}
1172 
1173 	parser_token_id = TOKEN_ID_PARAMETER;
1174 	return (AC_RET_LINE);
1175 }
1176 
1177 /*
1178  * Begin of the section.
1179  */
1180 static int
ac_bos(void)1181 ac_bos(void)
1182 {
1183 	if (sym_def_flag) {
1184 		/* Looks like incorrect definition of a macro variable. */
1185 		syntax_logmsgx("macro variable's value should be a "
1186 		    "string or wrong usage or macro variable definition");
1187 		return (AC_RET_ERR);
1188 	}
1189 
1190 	if (complete_conf_bufs() < 0)
1191 		return (AC_RET_ERR);
1192 
1193 	++section_cnt;
1194 
1195 	parser_token_id = TOKEN_ID_SECTION_BEGIN;
1196 	return (AC_RET_LINE);
1197 }
1198 
1199 /*
1200  * End of the section.
1201  */
1202 static int
ac_eos(void)1203 ac_eos(void)
1204 {
1205 	if (section_cnt == 0) {
1206 		syntax_logmsgx("unmatched closed curly brace");
1207 		return (AC_RET_ERR);
1208 	}
1209 
1210 	if (--section_cnt == 0)
1211 		free_sym_list(&local_sym_list);
1212 
1213 	parser_token_id = TOKEN_ID_SECTION_END;
1214 	return (AC_RET_LINE);
1215 }
1216 
1217 /*
1218  * '#' appeared, read current line up to the '\n' or EOF.
1219  */
1220 static int
ac_pnd(void)1221 ac_pnd(void)
1222 {
1223 	int rv;
1224 
1225 	for (;;) {
1226 		rv = parser_get_curchar();
1227 		if (rv != AC_RET_CONT)
1228 			return (rv);
1229 		if (*curchar == '\n')
1230 			break;
1231 	}
1232 
1233 	return (AC_RET_CONT);
1234 }
1235 
1236 /*
1237  * '\\' appeared in a string, check if the next character is either
1238  * '\"', '\\', 't', 'n' or '\n'.  If the next character is '\n', then
1239  * ignore '\\' and '\n'; if next character is 't' or 'n', then convert
1240  * this sequence to real character, else add "\\x" character sequence
1241  * to a string.
1242  */
1243 static int
ac_fmt(void)1244 ac_fmt(void)
1245 {
1246 	int rv;
1247 
1248 	rv = parser_get_curchar();
1249 	if (rv != AC_RET_CONT) {
1250 		if (rv == AC_RET_EOF)
1251 			syntax_error(ER_FMT);
1252 		return (rv);
1253 	}
1254 	switch (*curchar) {
1255 	case '\"':
1256 	case '\\':
1257 		if (parser_add_char('\\') < 0 ||
1258 		    parser_add_char(*curchar) < 0)
1259 			return (AC_RET_ERR);
1260 		break;
1261 	case 'n':
1262 		if (parser_add_char('\n') < 0)
1263 			return (AC_RET_ERR);
1264 		break;
1265 	case 't':
1266 		if (parser_add_char('\t') < 0)
1267 			return (AC_RET_ERR);
1268 		break;
1269 	case '\n':
1270 		break;
1271 	default:
1272 		syntax_error(ER_FMT);
1273 		return (AC_RET_ERR);
1274 	}
1275 
1276 	return (AC_RET_CONT);
1277 }
1278 
1279 /*
1280  * Space in the argument.
1281  */
1282 static int
ac_asp(void)1283 ac_asp(void)
1284 {
1285 	if (!was_space_flag && args_buf.off != 0) {
1286 		if (parser_add_char(' ') < 0)
1287 			return (AC_RET_ERR);
1288 		was_space_flag = 1;
1289 	}
1290 
1291 	return (AC_RET_CONT);
1292 }
1293 
1294 /*
1295  * '/' appeared.  To not make stac_tbl too complex here, there are
1296  * some tricks in checking if '/' can appear in current state.
1297  */
1298 static int
ac_slh(void)1299 ac_slh(void)
1300 {
1301 	int rv;
1302 
1303 	rv = parser_get_curchar();
1304 	if (rv != AC_RET_CONT) {
1305 		if (rv == AC_RET_EOF)
1306 			syntax_error(ER_UXP);
1307 		return (rv);
1308 	}
1309 
1310 	if (*curchar != '*') {
1311 		if (curstate == ST_INI || curstate == ST_TOK) {
1312 			syntax_error(ER_UXP);
1313 			return (AC_RET_ERR);
1314 		}
1315 		if (parser_add_char('/') < 0)
1316 			return (AC_RET_ERR);
1317 		if (parser_put_curchar() < 0)
1318 			return (AC_RET_ERR);
1319 		curstate = ST_ARG;
1320 		return (AC_RET_CONTX);
1321 	} else {
1322 		int was_star;
1323 
1324 		was_star = 0;
1325 		for (;;) {
1326 			rv = parser_get_curchar();
1327 			if (rv != AC_RET_CONT) {
1328 				if (rv == AC_RET_EOF) {
1329 					syntax_logmsgx("C-like comment is not "
1330 					    "closed");
1331 					return (AC_RET_ERR);
1332 				}
1333 				return (rv);
1334 			}
1335 			if (*curchar == '/') {
1336 				if (was_star)
1337 					break;
1338 			} else
1339 				was_star = *curchar == '*';
1340 		}
1341 		return (AC_RET_CONT);
1342 	}
1343 }
1344 
1345 /*
1346  * '$' appeared.  To not make stac_tbl too complex here, we will decide
1347  * if it is the begin of a macro variable definition or we need to
1348  * expand a macro variable.
1349  */
1350 static int
ac_dlr(void)1351 ac_dlr(void)
1352 {
1353 	struct conf_buf *next_buf;
1354 	int rv;
1355 
1356 	if (sym_def_flag) {
1357 		if (parser_add_char('$') < 0)
1358 			return (AC_RET_ERR);
1359 		return (AC_RET_CONT);
1360 	}
1361 	rv = parser_get_curchar();
1362 	if (rv != AC_RET_CONT) {
1363 		if (rv == AC_RET_EOF)
1364 			syntax_error(ER_UXP);
1365 		return (rv);
1366 	}
1367 	if (*curchar != '{') {
1368 		syntax_logmsgx("wrong usage of character '$': "
1369 		    "character '{' was missed");
1370 		return (AC_RET_ERR);
1371 	}
1372 
1373 	next_buf = cur_buf;
1374 
1375 	/* Get name of a macro variable. */
1376 	cur_buf = &sym_buf;
1377 	sym_buf.off = 0;
1378 	for (;;) {
1379 		rv = parser_get_curchar();
1380 		if (rv != AC_RET_CONT)
1381 			return (rv);
1382 		switch (whatis_char[*curchar]) {
1383 		case CH_UNDERSCORE:
1384 		case CH_ALPHA:
1385 		case CH_DIGIT:
1386 		case CH_DOLLAR:
1387 			if (parser_add_char(*curchar) < 0)
1388 				return (AC_RET_ERR);
1389 			break;
1390 		case CH_CLOSE_BRACE:
1391 			if (parser_add_char('\0') < 0)
1392 				return (AC_RET_ERR);
1393 			goto got_sym_name;
1394 		default:
1395 			syntax_logmsgx("this character is not allowed in "
1396 			    "macro variable name");
1397 			return (AC_RET_ERR);
1398 		}
1399 	}
1400 
1401 got_sym_name:
1402 	switch (sym_buf.off) {
1403 	case 1:
1404 		syntax_logmsgx("empty macro variable's name is not allowed");
1405 		return (AC_RET_ERR);
1406 	case 2:
1407 		if (sym_buf.buf[0] == '$') {
1408 			/* ${$} */
1409 			if (curstate == ST_INI) {
1410 				syntax_logmsgx("macro variable ${$} is "
1411 				    "reserved, cannot redefine it");
1412 				return (AC_RET_ERR);
1413 			}
1414 			/*
1415 			 * Expand ${$} right now to allow to insert
1416 			 * '$' character.
1417 			 */
1418 			cur_buf = &args_buf;
1419 			if (parser_add_char('$') < 0)
1420 				return (AC_RET_ERR);
1421 			break;
1422 		}
1423 		/* FALLTHROUGH */
1424 	default:
1425 		if (curstate == ST_INI) {
1426 			/* Symbol definition. */
1427 			next_buf = &args_buf;
1428 			sym_def_flag = 1;
1429 		} else {
1430 			/* Expand symbol. */
1431 			struct parser_pb *pb;
1432 			struct psym *psym;
1433 
1434 			psym = find_sym(&local_sym_list, sym_buf.buf);
1435 			if (psym == NULL)
1436 				psym = find_sym(&global_sym_list, sym_buf.buf);
1437 			if (psym == NULL) {
1438 				syntax_logmsgx("unknown macro variable ${%s}",
1439 				    sym_buf.buf);
1440 				return (AC_RET_ERR);
1441 			}
1442 			pb = parser_new_pb(psym->val_len);
1443 			if (pb == NULL) {
1444 				error_logmsgx("cannot expand macro variable "
1445 				    "${%s}", sym_buf.buf);
1446 				return (AC_RET_ERR);
1447 			}
1448 			pb->buf = (unsigned char *)psym->val;
1449 			pb->buf_nread = psym->val_len;
1450 			pb->sname = psym->sym;
1451 			if (parser_push_pb(pb) < 0)
1452 				return (AC_RET_ERR);
1453 		}
1454 	}
1455 
1456 	cur_buf = next_buf;
1457 
1458 	return (AC_RET_CONT);
1459 }
1460 
1461 /*
1462  * A syntax error occurred.
1463  */
1464 static int
ac_err(void)1465 ac_err(void)
1466 {
1467 	syntax_error(curstac->state);
1468 	return (AC_RET_ERR);
1469 }
1470 
1471 /*
1472  * Return non-zero if the given buffer is a string.
1473  */
1474 int
parser_buf_is_str(const char * s)1475 parser_buf_is_str(const char *s)
1476 {
1477 	if (*s != '\"')
1478 		return (0);
1479 
1480 	for (++s; *s != '\0'; ++s)
1481 		switch (*s) {
1482 		case '\\':
1483 			/* "\\x" */
1484 			++s;
1485 			break;
1486 		case '\"':
1487 			/* Last '\"'. */
1488 			return (*(s + 1) == '\0');
1489 		}
1490 
1491 	return (0);
1492 }
1493 
1494 /*
1495  * Return non-zero if whole parser_args is a string.
1496  * This is the optimized version, no linear search.
1497  */
1498 int
parser_arg_is_str(void)1499 parser_arg_is_str(void)
1500 {
1501 	return (parser_nargs == 1 && quotes_cnt == 2 &&
1502 	    parser_args[0] == '\"' && parser_args[parser_args_len - 1] == '\"');
1503 }
1504 
1505 /*
1506  * Allocate buffer and copy string from str converting '\\'-like
1507  * sequences to real characters (thanks to ac_fmt() there are only
1508  * two such sequences \" and \\).
1509  */
1510 char *
parser_strdup(char * str,void * mem_type)1511 parser_strdup(char *str, void *mem_type)
1512 {
1513 	const char *ptr;
1514 	char *res, *mod;
1515 	size_t len;
1516 	int has_special;
1517 
1518 	len = strlen(str) - 1;
1519 	res = mem_malloc(len, mem_type);
1520 	if (res == NULL) {
1521 		error_logmsgx("parser_strdup: mem_malloc failed");
1522 		return (NULL);
1523 	}
1524 	has_special = 0;
1525 	for (mod = res, ptr = str + 1, len = 0; *ptr != '\"';
1526 	    ++len, ++mod, ++ptr)
1527 		if (*ptr == '\\') {
1528 			has_special = 1;
1529 			/* \" or \\. */
1530 			*mod = *++ptr;
1531 		} else
1532 			*mod = *ptr;
1533 
1534 	*mod = '\0';
1535 	if (has_special) {
1536 		mod = mem_realloc(res, ++len, mem_type);
1537 		if (mod == NULL)
1538 			error_logmsgx("parser_strdup: mem_realloc failed");
1539 		res = mod;
1540 	}
1541 	return (res);
1542 }
1543 
1544 /*
1545  * Allocate buffer and convert a string pointed by ptr to
1546  * this buffer expanding all escape sequences.
1547  */
1548 char *
parser_stringify(const char * ptr)1549 parser_stringify(const char *ptr)
1550 {
1551 	char *mod;
1552 
1553 	/* Can free, because mem_malloc can return NULL. */
1554 	mem_free(parser_str_buf, m_parser);
1555 
1556 	/* Allocated buffer will be bigger than really needed. */
1557 	parser_str_buf = mem_malloc(2 + 1 + strlen(ptr) * 2, m_parser);
1558 	if (parser_str_buf == NULL)
1559 		return ("(parser_stringify: mem_malloc failed)");
1560 
1561 	*parser_str_buf = '\"';
1562 
1563 	for (mod = parser_str_buf + 1; *ptr != '\0'; ++mod, ++ptr)
1564 		switch (*ptr) {
1565 		case '\\':
1566 		case '\"':
1567 		case '\t':
1568 		case '\n':
1569 			*mod = '\\';
1570 			++mod;
1571 			/* FALLTHROUGH */
1572 		default:
1573 			*mod = *ptr;
1574 		}
1575 
1576 	*mod = '\"';
1577 	*(mod + 1) = '\0';
1578 
1579 	return (parser_str_buf);
1580 }
1581