--- /dev/null
+/*
+ * MAP management functions.
+ *
+ * Copyright 2000-2013 Willy Tarreau <w@1wt.eu>
+ *
+ * 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 <limits.h>
+#include <stdio.h>
+
+#include <common/standard.h>
+
+#include <types/global.h>
+#include <types/map.h>
+
+#include <proto/arg.h>
+#include <proto/pattern.h>
+#include <proto/sample.h>
+
+/* This function return existing map reference or return NULL. */
+static struct map_reference *map_get_reference(const char *reference)
+{
+ struct map_reference *ref;
+
+ /* process the lookup */
+ list_for_each_entry(ref, &maps, list)
+ if (strcmp(ref->reference, reference) == 0)
+ return ref;
+ return NULL;
+}
+
+/* Parse an IPv4 address and store it into the sample.
+ * The output type is IPV4.
+ */
+static int map_parse_ip(const char *text, struct sample_storage *smp)
+{
+ if (!buf2ip(text, strlen(text), &smp->data.ipv4))
+ return 0;
+ smp->type = SMP_T_IPV4;
+ return 1;
+}
+
+/* Parse an IPv6 address and store it into the sample.
+ * The output type is IPV6.
+ */
+static int map_parse_ip6(const char *text, struct sample_storage *smp)
+{
+ if (!buf2ip6(text, strlen(text), &smp->data.ipv6))
+ return 0;
+ smp->type = SMP_T_IPV6;
+ return 1;
+}
+
+/* Parse a string and store a pointer to it into the sample. The original
+ * string must be left in memory because we return a direct memory reference.
+ * The output type is CSTR.
+ */
+static int map_parse_str(const char *text, struct sample_storage *smp)
+{
+ /* The loose of the "const" is balanced by the SMP_T_CSTR type */
+ smp->data.str.str = (char *)text;
+ smp->data.str.len = strlen(text);
+ smp->data.str.size = smp->data.str.len + 1;
+ smp->type = SMP_T_CSTR;
+ return 1;
+}
+
+/* Parse an integer and convert it to a sample. The output type is SINT if the
+ * number is negative, or UINT if it is positive or null. The function returns
+ * zero (error) if the number is too large.
+ */
+static int map_parse_int(const char *text, struct sample_storage *smp)
+{
+ long long int value;
+ char *error;
+
+ /* parse interger and convert it. Return the value in 64 format. */
+ value = strtoll(text, &error, 10);
+ if (*error != '\0')
+ return 0;
+
+ /* check sign iand limits */
+ if (value < 0) {
+ if (value < INT_MIN)
+ return 0;
+ smp->type = SMP_T_SINT;
+ smp->data.sint = value;
+ }
+ else {
+ if (value > UINT_MAX)
+ return 0;
+ smp->type = SMP_T_UINT;
+ smp->data.uint = value;
+ }
+
+ return 1;
+}
+
+/* This function creates and initializes a new map_reference entry. This
+ * function only fails in case of a memory allocation issue, in which case
+ * it returns NULL. <reference> here is a unique identifier for the map's
+ * contents, typically the name of the file used to build the map.
+ */
+static struct map_reference *map_create_reference(const char *reference)
+{
+ struct map_reference *ref;
+
+ /* create new entry */
+ ref = calloc(1, sizeof(*ref));
+ if (!ref)
+ return NULL;
+
+ ref->reference = strdup(reference);
+ if (!ref->reference)
+ return NULL;
+
+ LIST_INIT(&ref->entries);
+ LIST_INIT(&ref->maps);
+ LIST_ADDQ(&maps, &ref->list);
+
+ return ref;
+}
+
+/* This function just create new entry */
+static struct map_entry *map_create_entry(int line, char *key, char *value)
+{
+ struct map_entry *ent;
+
+ ent = calloc(1, sizeof(*ent));
+ if (!ent)
+ return NULL;
+
+ ent->line = line;
+
+ ent->key = strdup(key);
+ if (!ent->key) {
+ free(ent);
+ return NULL;
+ }
+
+ ent->value = strdup(value);
+ if (!ent->value) {
+ free(ent->key);
+ free(ent);
+ return NULL;
+ }
+
+ return ent;
+}
+
+/* This crete and initialize map descriptor.
+ * Return NULL if out of memory error
+ */
+static struct map_descriptor *map_create_descriptor(struct map_reference *ref,
+ struct sample_conv *conv)
+{
+ struct map_descriptor *desc;
+
+ desc = calloc(1, sizeof(*desc));
+ if (!desc)
+ return NULL;
+
+ desc->conv = conv;
+
+ LIST_ADDQ(&ref->maps, &desc->list);
+
+ return desc;
+}
+
+/* This function just add entry into the list of pattern.
+ * It can return false only in memory problem case
+ */
+static int map_add_entry(struct map_reference *map, int line, char *key, char *value)
+{
+ struct map_entry *ent;
+
+ ent = map_create_entry(line, key, value);
+ if (!ent)
+ return 0;
+ LIST_ADDQ(&map->entries, &ent->list);
+ return 1;
+}
+
+/* Reads patterns from a file. If <err_msg> is non-NULL, an error message will
+ * be returned there on errors and the caller will have to free it.
+ *
+ * The file contains one key + value per line. Lines which start with '#' are
+ * ignored, just like empty lines. Leading tabs/spaces are stripped. The key is
+ * then the first "word" (series of non-space/tabs characters), and the value is
+ * what follows this series of space/tab till the end of the line excluding
+ * trailing spaces/tabs.
+ *
+ * Example :
+ *
+ * # this is a comment and is ignored
+ * 62.212.114.60 1wt.eu \n
+ * <-><-----------><---><----><---->
+ * | | | | `--- trailing spaces ignored
+ * | | | `-------- value
+ * | | `--------------- middle spaces ignored
+ * | `------------------------ key
+ * `-------------------------------- leading spaces ignored
+ *
+ * Return non-zero in case of succes, otherwise 0.
+ */
+static int map_read_entries_from_file(const char *filename,
+ struct map_reference *ref,
+ char **err)
+{
+ FILE *file;
+ char *c;
+ int ret = 0;
+ int line = 0;
+ char *key_beg;
+ char *key_end;
+ char *value_beg;
+ char *value_end;
+
+ file = fopen(filename, "r");
+ if (!file) {
+ memprintf(err, "failed to open pattern file <%s>", filename);
+ return 0;
+ }
+
+ /* now parse all patterns. The file may contain only one pattern
+ * followed by one value per line. The start spaces, separator spaces
+ * and and spaces are stripped. Each can contain comment started by '#'
+ */
+ while (fgets(trash.str, trash.size, file) != NULL) {
+ line++;
+ c = trash.str;
+
+ /* ignore lines beginning with a dash */
+ if (*c == '#')
+ continue;
+
+ /* strip leading spaces and tabs */
+ while (*c == ' ' || *c == '\t')
+ c++;
+
+ /* empty lines are ignored too */
+ if (*c == '\0')
+ continue;
+
+ /* look for the end of the key */
+ key_beg = c;
+ while (*c && *c != ' ' && *c != '\t' && *c != '\n' && *c != '\r')
+ c++;
+
+ key_end = c;
+
+ /* strip middle spaces and tabs */
+ while (*c == ' ' || *c == '\t')
+ c++;
+
+ /* look for the end of the value, it is the end of the line */
+ value_beg = c;
+ while (*c && *c != '\n' && *c != '\r')
+ c++;
+ value_end = c;
+
+ /* trim possibly trailing spaces and tabs */
+ while (value_end > value_beg && (value_end[-1] == ' ' || value_end[-1] == '\t'))
+ value_end--;
+
+ /* set final \0 and check entries */
+ *key_end = '\0';
+ *value_end = '\0';
+
+ /* insert values */
+ if (!map_add_entry(ref, line, key_beg, value_beg)) {
+ memprintf(err, "out of memory");
+ goto out_close;
+ }
+ }
+
+ /* succes */
+ ret = 1;
+
+ out_close:
+ fclose(file);
+ return ret;
+}
+
+/* This function read the string entries of <ent>, parse it with
+ * the <desc> methods, and strore the result into <desc> dummy ACL.
+ * return 1 in succes case, else return 0 and <err> is filled.
+ *
+ * The acm parser use <pattern> for creating new pattern (list
+ * of values case) or using the same pattern (tree index case).
+ *
+ * <patflags> must be PAT_F_*.
+ */
+static int map_parse_and_index(struct map_descriptor *desc,
+ struct pattern **pattern,
+ struct map_entry *ent,
+ int patflags,
+ char **err)
+{
+ struct sample_storage *smp;
+
+ /* use new smp for storing value */
+ smp = calloc(1, sizeof(*smp));
+ if (!smp)
+ return 0;
+
+ /* first read and convert value */
+ if (!desc->parse(ent->value, smp)) {
+ memprintf(err, "parse value failed at line %d of file <%s>",
+ ent->line, desc->ref->reference);
+ return 0;
+ }
+
+ /* read and convert key */
+ if (!pattern_register(&desc->pat, ent->key, smp, pattern, patflags, err))
+ return 0;
+
+ return 1;
+}
+
+/* This function load the map file according with data type declared into
+ * the "struct sample_conv".
+ *
+ * This function choose the indexation type (ebtree or list) according with
+ * the type of match needed.
+ */
+static int sample_load_map(struct arg *arg, struct sample_conv *conv, char **err)
+{
+ struct map_reference *ref;
+ struct map_descriptor *desc;
+ struct pattern *pattern;
+ struct map_entry *ent;
+
+ /* look for existing map reference. The reference is the
+ * file encountered in the first argument. arg[0] with string
+ * type is guaranteed by the parser.
+ */
+ ref = map_get_reference(arg[0].data.str.str);
+
+ /* The reference doesn't exist */
+ if (!ref) {
+
+ /* create new reference entry */
+ ref = map_create_reference(arg[0].data.str.str);
+ if (!ref) {
+ memprintf(err, "out of memory");
+ return 0;
+ }
+
+ /* load the file */
+ if (!map_read_entries_from_file(arg[0].data.str.str, ref, err))
+ return 0;
+ }
+
+ /* create new map descriptor */
+ desc = map_create_descriptor(ref, conv);
+ if (!desc) {
+ memprintf(err, "out of memory");
+ return 0;
+ }
+
+ desc->ref = ref;
+ pattern_init_expr(&desc->pat);
+
+ /* set the match method */
+ desc->pat.match = pat_match_fcts[conv->private];
+
+ /* set the input parse method */
+ switch (conv->in_type) {
+ case SMP_T_STR: desc->pat.parse = pat_parse_fcts[PAT_MATCH_STR]; break;
+ case SMP_T_UINT: desc->pat.parse = pat_parse_fcts[PAT_MATCH_INT]; break;
+ case SMP_T_ADDR: desc->pat.parse = pat_parse_fcts[PAT_MATCH_IP]; break;
+ default:
+ memprintf(err, "map: internal haproxy error: no default parse case for the input type <%d>.",
+ conv->in_type);
+ return 0;
+ }
+
+ /* check the output parse method */
+ switch (desc->conv->out_type) {
+ case SMP_T_STR: desc->parse = map_parse_str; break;
+ case SMP_T_UINT: desc->parse = map_parse_int; break;
+ case SMP_T_IPV4: desc->parse = map_parse_ip; break;
+ case SMP_T_IPV6: desc->parse = map_parse_ip6; break;
+ default:
+ memprintf(err, "map: internal haproxy error: no default parse case for the input type <%d>.",
+ conv->out_type);
+ return 0;
+ }
+
+ /* The second argument is the default value */
+ if (arg[1].type == ARGT_STR) {
+ desc->default_value = strdup(arg[1].data.str.str);
+ if (!desc->default_value) {
+ memprintf(err, "out of memory");
+ return 0;
+ }
+ desc->def = calloc(1, sizeof(*desc->def));
+ if (!desc->def) {
+ memprintf(err, "out of memory");
+ return 0;
+ }
+ if (!desc->parse(desc->default_value, desc->def)) {
+ memprintf(err, "Cannot parse default value");
+ return 0;
+ }
+ }
+ else
+ desc->def = NULL;
+
+ /* parse each line of the file */
+ pattern = NULL;
+ list_for_each_entry(ent, &ref->entries, list)
+ if (!map_parse_and_index(desc, &pattern, ent, 0, err))
+ return 0;
+
+ /* replace the first argument by this definition */
+ arg[0].type = ARGT_MAP;
+ arg[0].data.map = desc;
+
+ return 1;
+}
+
+static int sample_conv_map(const struct arg *arg_p, struct sample *smp)
+{
+ struct map_descriptor *desc;
+ struct sample_storage *sample;
+ enum pat_match_res ret;
+
+ /* get config */
+ desc = arg_p[0].data.map;
+
+ /* Execute the match function. */
+ ret = pattern_exec_match(&desc->pat, smp, &sample);
+ if (ret != PAT_MATCH) {
+ if (!desc->def)
+ return 0;
+ sample = desc->def;
+ }
+
+ /* copy new data */
+ smp->type = sample->type;
+ memcpy(&smp->data, &sample->data, sizeof(smp->data));
+ return 1;
+}
+
+/* Note: must not be declared <const> as its list will be overwritten
+ *
+ * For the map_*_int keywords, the output is declared as SMP_T_UINT, but the converter function
+ * can provide SMP_T_UINT, SMP_T_SINT or SMP_T_BOOL depending on how the patterns found in the
+ * file can be parsed.
+ *
+ * For the map_*_ip keyword, the output is declared as SMP_T_IPV4, but the converter function
+ * can provide SMP_T_IPV4 or SMP_T_IPV6 depending on the patterns found in the file.
+ *
+ * The map_* keywords only emit strings.
+ *
+ * The output type is only used during the configuration parsing. It is used for detecting
+ * compatibility problems.
+ *
+ * The arguments are: <file>[,<default value>]
+ */
+static struct sample_conv_kw_list sample_conv_kws = {ILH, {
+ { "map", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_STR, PAT_MATCH_STR },
+ { "map_str", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_STR, PAT_MATCH_STR },
+ { "map_beg", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_STR, PAT_MATCH_BEG },
+ { "map_sub", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_STR, PAT_MATCH_SUB },
+ { "map_dir", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_STR, PAT_MATCH_DIR },
+ { "map_dom", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_STR, PAT_MATCH_DOM },
+ { "map_end", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_STR, PAT_MATCH_END },
+ { "map_reg", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_STR, PAT_MATCH_REG },
+ { "map_int", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_UINT, SMP_T_STR, PAT_MATCH_INT },
+ { "map_ip", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_ADDR, SMP_T_STR, PAT_MATCH_IP },
+
+ { "map_str_int", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_UINT, PAT_MATCH_STR },
+ { "map_beg_int", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_UINT, PAT_MATCH_BEG },
+ { "map_sub_int", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_UINT, PAT_MATCH_SUB },
+ { "map_dir_int", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_UINT, PAT_MATCH_DIR },
+ { "map_dom_int", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_UINT, PAT_MATCH_DOM },
+ { "map_end_int", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_UINT, PAT_MATCH_END },
+ { "map_reg_int", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_UINT, PAT_MATCH_REG },
+ { "map_int_int", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_UINT, SMP_T_UINT, PAT_MATCH_INT },
+ { "map_ip_int", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_ADDR, SMP_T_UINT, PAT_MATCH_IP },
+
+ { "map_str_ip", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_IPV4, PAT_MATCH_STR },
+ { "map_beg_ip", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_IPV4, PAT_MATCH_BEG },
+ { "map_sub_ip", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_IPV4, PAT_MATCH_SUB },
+ { "map_dir_ip", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_IPV4, PAT_MATCH_DIR },
+ { "map_dom_ip", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_IPV4, PAT_MATCH_DOM },
+ { "map_end_ip", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_IPV4, PAT_MATCH_END },
+ { "map_reg_ip", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_STR, SMP_T_IPV4, PAT_MATCH_REG },
+ { "map_int_ip", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_UINT, SMP_T_IPV4, PAT_MATCH_INT },
+ { "map_ip_ip", sample_conv_map, ARG2(1,STR,STR), sample_load_map, SMP_T_ADDR, SMP_T_IPV4, PAT_MATCH_IP },
+
+ { /* END */ },
+}};
+
+__attribute__((constructor))
+static void __map_init(void)
+{
+ /* register format conversion keywords */
+ sample_register_convs(&sample_conv_kws);
+}