/* * Functions used to parse typed argument lists * * Copyright 2012 Willy Tarreau * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * */ #include #include #include #include #include #include #include #include const char *arg_type_names[ARGT_NBTYPES] = { [ARGT_STOP] = "end of arguments", [ARGT_SINT] = "integer", [ARGT_STR] = "string", [ARGT_IPV4] = "IPv4 address", [ARGT_MSK4] = "IPv4 mask", [ARGT_IPV6] = "IPv6 address", [ARGT_MSK6] = "IPv6 mask", [ARGT_TIME] = "delay", [ARGT_SIZE] = "size", [ARGT_FE] = "frontend", [ARGT_BE] = "backend", [ARGT_TAB] = "table", [ARGT_SRV] = "server", [ARGT_USR] = "user list", [ARGT_MAP] = "map", [ARGT_REG] = "regex", [ARGT_VAR] = "variable", [ARGT_PBUF_FNUM] = "Protocol buffers field number", /* Unassigned types must never happen. Better crash during parsing if they do. */ }; /* This dummy arg list may be used by default when no arg is found, it helps * parsers by removing pointer checks. */ struct arg empty_arg_list[ARGM_NBARGS] = { }; /* This function clones a struct arg_list template into a new one which is * returned. */ struct arg_list *arg_list_clone(const struct arg_list *orig) { struct arg_list *new; if ((new = calloc(1, sizeof(*new))) != NULL) { /* ->list will be set by the caller when inserting the element. * ->arg and ->arg_pos will be set by the caller. */ new->ctx = orig->ctx; new->kw = orig->kw; new->conv = orig->conv; new->file = orig->file; new->line = orig->line; } return new; } /* This function clones a struct template into a new one which is * set to point to arg at pos , and which is returned if the caller * wants to apply further changes. */ struct arg_list *arg_list_add(struct arg_list *orig, struct arg *arg, int pos) { struct arg_list *new; new = arg_list_clone(orig); if (new) { new->arg = arg; new->arg_pos = pos; LIST_APPEND(&orig->list, &new->list); } return new; } /* This function builds an argument list from a config line, and stops at the * first non-matching character, which is pointed to in . A valid arg * list starts with an opening parenthesis '(', contains a number of comma- * delimited words, and ends with the closing parenthesis ')'. An empty list * (with or without the parenthesis) will lead to a valid empty argument if the * keyword has a mandatory one. The function returns the number of arguments * emitted, or <0 in case of any error. Everything needed it automatically * allocated. A pointer to an error message might be returned in err_msg if not * NULL, in which case it would be allocated and the caller will have to check * it and free it. The output arg list is returned in argp which must be valid. * The returned array is always terminated by an arg of type ARGT_STOP (0), * unless the mask indicates that no argument is supported. Unresolved arguments * are appended to arg list , which also serves as a template to create new * entries. may be NULL if unresolved arguments are not allowed. The mask * is composed of a number of mandatory arguments in its lower ARGM_BITS bits, * and a concatenation of each argument type in each subsequent ARGT_BITS-bit * sblock. If is not NULL, it must point to a freeable or NULL * pointer. The caller is expected to restart the parsing from the new pointer * set in , which is the first character considered as not being part * of the arg list. The input string ends on the first between characters * (when len is positive) or the first NUL character. Placing -1 in will * make it virtually unbounded (~2GB long strings). */ int make_arg_list(const char *in, int len, uint64_t mask, struct arg **argp, char **err_msg, const char **end_ptr, int *err_arg, struct arg_list *al) { int nbarg; int pos; struct arg *arg; const char *beg; const char *ptr_err = NULL; int min_arg; int empty; struct arg_list *new_al = al; *argp = NULL; empty = 0; if (!len || *in != '(') { /* it's already not for us, stop here */ empty = 1; len = 0; } else { /* skip opening parenthesis */ len--; in++; } min_arg = mask & ARGM_MASK; mask >>= ARGM_BITS; pos = 0; /* find between 0 and NBARGS the max number of args supported by the mask */ for (nbarg = 0; nbarg < ARGM_NBARGS && ((mask >> (nbarg * ARGT_BITS)) & ARGT_MASK); nbarg++); if (!nbarg) goto end_parse; /* Note: an empty input string contains an empty argument if this argument * is marked mandatory. Otherwise we can ignore it. */ if (empty && !min_arg) goto end_parse; arg = *argp = calloc(nbarg + 1, sizeof(**argp)); if (!arg) goto alloc_err; /* Note: empty arguments after a comma always exist. */ while (pos < nbarg) { unsigned int uint; int squote = 0, dquote = 0; char *out; chunk_reset(&trash); out = trash.area; while (len && *in && trash.data < trash.size - 1) { if (*in == '"' && !squote) { /* double quote outside single quotes */ if (dquote) dquote = 0; else dquote = 1; in++; len--; continue; } else if (*in == '\'' && !dquote) { /* single quote outside double quotes */ if (squote) squote = 0; else squote = 1; in++; len--; continue; } else if (*in == '\\' && !squote && len != 1) { /* '\', ', ' ', '"' support being escaped by '\' */ if (in[1] == 0) goto unquote_err; if (in[1] == '\\' || in[1] == ' ' || in[1] == '"' || in[1] == '\'') { in++; len--; *out++ = *in; } else if (in[1] == 'r') { in++; len--; *out++ = '\r'; } else if (in[1] == 'n') { in++; len--; *out++ = '\n'; } else if (in[1] == 't') { in++; len--; *out++ = '\t'; } else { /* just a lone '\' */ *out++ = *in; } in++; len--; } else { if (!squote && !dquote && (*in == ',' || *in == ')')) { /* end of argument */ break; } /* verbatim copy */ *out++ = *in++; len--; } trash.data = out - trash.area; } if (len && *in && *in != ',' && *in != ')') goto buffer_err; trash.area[trash.data] = 0; arg->type = (mask >> (pos * ARGT_BITS)) & ARGT_MASK; switch (arg->type) { case ARGT_SINT: if (!trash.data) // empty number goto empty_err; beg = trash.area; arg->data.sint = read_int64(&beg, trash.area + trash.data); if (beg < trash.area + trash.data) goto parse_err; arg->type = ARGT_SINT; break; case ARGT_FE: case ARGT_BE: case ARGT_TAB: case ARGT_SRV: case ARGT_USR: case ARGT_REG: /* These argument types need to be stored as strings during * parsing then resolved later. */ if (!al) goto resolve_err; arg->unresolved = 1; new_al = arg_list_add(al, arg, pos); __fallthrough; case ARGT_STR: /* all types that must be resolved are stored as strings * during the parsing. The caller must at one point resolve * them and free the string. */ arg->data.str.area = my_strndup(trash.area, trash.data); arg->data.str.data = trash.data; arg->data.str.size = trash.data + 1; break; case ARGT_IPV4: if (!trash.data) // empty address goto empty_err; if (inet_pton(AF_INET, trash.area, &arg->data.ipv4) <= 0) goto parse_err; break; case ARGT_MSK4: if (!trash.data) // empty mask goto empty_err; if (!str2mask(trash.area, &arg->data.ipv4)) goto parse_err; arg->type = ARGT_IPV4; break; case ARGT_IPV6: if (!trash.data) // empty address goto empty_err; if (inet_pton(AF_INET6, trash.area, &arg->data.ipv6) <= 0) goto parse_err; break; case ARGT_MSK6: if (!trash.data) // empty mask goto empty_err; if (!str2mask6(trash.area, &arg->data.ipv6)) goto parse_err; arg->type = ARGT_IPV6; break; case ARGT_TIME: if (!trash.data) // empty time goto empty_err; ptr_err = parse_time_err(trash.area, &uint, TIME_UNIT_MS); if (ptr_err) { if (ptr_err == PARSE_TIME_OVER || ptr_err == PARSE_TIME_UNDER) ptr_err = trash.area; goto parse_err; } arg->data.sint = uint; arg->type = ARGT_SINT; break; case ARGT_SIZE: if (!trash.data) // empty size goto empty_err; ptr_err = parse_size_err(trash.area, &uint); if (ptr_err) goto parse_err; arg->data.sint = uint; arg->type = ARGT_SINT; break; case ARGT_PBUF_FNUM: if (!trash.data) goto empty_err; if (!parse_dotted_uints(trash.area, &arg->data.fid.ids, &arg->data.fid.sz)) goto parse_err; break; /* FIXME: other types need to be implemented here */ default: goto not_impl; } pos++; arg++; /* don't go back to parsing if we reached end */ if (!len || !*in || *in == ')' || pos >= nbarg) break; /* skip comma */ in++; len--; } end_parse: if (pos < min_arg) { /* not enough arguments */ memprintf(err_msg, "missing arguments (got %d/%d), type '%s' expected", pos, min_arg, arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK]); goto err; } if (empty) { /* nothing to do */ } else if (*in == ')') { /* skip the expected closing parenthesis */ in++; } else { /* the caller is responsible for freeing this message */ char *word = (len > 0) ? my_strndup(in, len) : (char *)in; if (*word) memprintf(err_msg, "expected ')' before '%s'", word); else memprintf(err_msg, "expected ')'"); if (len > 0) free(word); /* when we're missing a right paren, the empty part preceding * already created an empty arg, adding one to the position, so * let's fix the reporting to avoid being confusing. */ if (pos > 1) pos--; goto err; } /* note that pos might be < nbarg and this is not an error, it's up to the * caller to decide what to do with optional args. */ if (err_arg) *err_arg = pos; if (end_ptr) *end_ptr = in; return pos; err: if (new_al == al) { /* only free the arg area if we have not queued unresolved args * still pointing to it. */ free_args(*argp); free(*argp); } *argp = NULL; if (err_arg) *err_arg = pos; if (end_ptr) *end_ptr = in; return -1; empty_err: /* If we've only got an empty set of parenthesis with nothing * in between, there is no arg at all. */ if (!pos) { ha_free(argp); } if (pos >= min_arg) goto end_parse; memprintf(err_msg, "expected type '%s' at position %d, but got nothing", arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1); goto err; parse_err: /* come here with the word attempted to parse in trash */ memprintf(err_msg, "failed to parse '%s' as type '%s' at position %d", trash.area, arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1); goto err; not_impl: memprintf(err_msg, "parsing for type '%s' was not implemented, please report this bug", arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK]); goto err; buffer_err: memprintf(err_msg, "too small buffer size to store decoded argument %d, increase bufsize ?", pos + 1); goto err; unquote_err: /* come here with the parsed part in : and the * unparsable part in . */ trash.area[trash.data] = 0; memprintf(err_msg, "failed to parse '%s' after '%s' as type '%s' at position %d", in, trash.area, arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1); goto err; alloc_err: memprintf(err_msg, "out of memory"); goto err; resolve_err: memprintf(err_msg, "unresolved argument of type '%s' at position %d not allowed", arg_type_names[(mask >> (pos * ARGT_BITS)) & ARGT_MASK], pos + 1); goto err; } /* Free all args of an args array, taking care of unresolved arguments as well. * It stops at the ARGT_STOP, which must be present. The array itself is not * freed, it's up to the caller to do it. However it is returned, allowing to * call free(free_args(argptr)). It is valid to call it with a NULL args, and * nothing will be done). */ struct arg *free_args(struct arg *args) { struct arg *arg; for (arg = args; arg && arg->type != ARGT_STOP; arg++) { if (arg->type == ARGT_STR || arg->unresolved) chunk_destroy(&arg->data.str); else if (arg->type == ARGT_REG) regex_free(arg->data.reg); else if (arg->type == ARGT_PBUF_FNUM) ha_free(&arg->data.fid.ids); } return args; }