/* * Configuration parser * * Copyright 2000-2011 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. * */ #ifdef USE_LIBCRYPT /* This is to have crypt() defined on Linux */ #define _GNU_SOURCE #ifdef USE_CRYPT_H /* some platforms such as Solaris need this */ #include #endif #endif /* USE_LIBCRYPT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Used to chain configuration sections definitions. This list * stores struct cfg_section */ struct list sections = LIST_HEAD_INIT(sections); struct list postparsers = LIST_HEAD_INIT(postparsers); char *cursection = NULL; struct proxy defproxy = { }; /* fake proxy used to assign default values on all instances */ int cfg_maxpconn = 0; /* # of simultaneous connections per proxy (-N) */ int cfg_maxconn = 0; /* # of simultaneous connections, (-n) */ char *cfg_scope = NULL; /* the current scope during the configuration parsing */ /* List head of all known configuration keywords */ struct cfg_kw_list cfg_keywords = { .list = LIST_HEAD_INIT(cfg_keywords.list) }; /* * converts to a list of listeners which are dynamically allocated. * The format is "{addr|'*'}:port[-end][,{addr|'*'}:port[-end]]*", where : * - can be empty or "*" to indicate INADDR_ANY ; * - is a numerical port from 1 to 65535 ; * - indicates to use the range from to instead (inclusive). * This can be repeated as many times as necessary, separated by a coma. * Function returns 1 for success or 0 if error. In case of errors, if is * not NULL, it must be a valid pointer to either NULL or a freeable area that * will be replaced with an error message. */ int str2listener(char *str, struct proxy *curproxy, struct bind_conf *bind_conf, const char *file, int line, char **err) { char *next, *dupstr; int port, end; next = dupstr = strdup(str); while (next && *next) { int inherited = 0; struct sockaddr_storage *ss2; int fd = -1; str = next; /* 1) look for the end of the first address */ if ((next = strchr(str, ',')) != NULL) { *next++ = 0; } ss2 = str2sa_range(str, NULL, &port, &end, err, curproxy == global.stats_fe ? NULL : global.unix_bind.prefix, NULL, 1); if (!ss2) goto fail; if (ss2->ss_family == AF_INET || ss2->ss_family == AF_INET6 || ss2->ss_family == AF_CUST_UDP4 || ss2->ss_family == AF_CUST_UDP6) { if (!port && !end) { memprintf(err, "missing port number: '%s'\n", str); goto fail; } if (!port || !end) { memprintf(err, "port offsets are not allowed in 'bind': '%s'\n", str); goto fail; } if (port < 1 || port > 65535) { memprintf(err, "invalid port '%d' specified for address '%s'.\n", port, str); goto fail; } if (end < 1 || end > 65535) { memprintf(err, "invalid port '%d' specified for address '%s'.\n", end, str); goto fail; } } else if (ss2->ss_family == AF_CUST_EXISTING_FD) { socklen_t addr_len; inherited = 1; /* We want to attach to an already bound fd whose number * is in the addr part of ss2 when cast to sockaddr_in. * Note that by definition there is a single listener. * We still have to determine the address family to * register the correct protocol. */ fd = ((struct sockaddr_in *)ss2)->sin_addr.s_addr; addr_len = sizeof(*ss2); if (getsockname(fd, (struct sockaddr *)ss2, &addr_len) == -1) { memprintf(err, "cannot use file descriptor '%d' : %s.\n", fd, strerror(errno)); goto fail; } port = end = get_host_port(ss2); } else if (ss2->ss_family == AF_CUST_SOCKPAIR) { socklen_t addr_len; inherited = 1; fd = ((struct sockaddr_in *)ss2)->sin_addr.s_addr; addr_len = sizeof(*ss2); if (getsockname(fd, (struct sockaddr *)ss2, &addr_len) == -1) { memprintf(err, "cannot use file descriptor '%d' : %s.\n", fd, strerror(errno)); goto fail; } ss2->ss_family = AF_CUST_SOCKPAIR; /* reassign AF_CUST_SOCKPAIR because of getsockname */ port = end = 0; } /* OK the address looks correct */ if (!create_listeners(bind_conf, ss2, port, end, fd, inherited, err)) { memprintf(err, "%s for address '%s'.\n", *err, str); goto fail; } } /* end while(next) */ free(dupstr); return 1; fail: free(dupstr); return 0; } /* * Report an error in when there are too many arguments. This version is * intended to be used by keyword parsers so that the message will be included * into the general error message. The index is the current keyword in args. * Return 0 if the number of argument is correct, otherwise build a message and * return 1. Fill err_code with an ERR_ALERT and an ERR_FATAL if not null. The * message may also be null, it will simply not be produced (useful to check only). * and are only affected on error. */ int too_many_args_idx(int maxarg, int index, char **args, char **msg, int *err_code) { int i; if (!*args[index + maxarg + 1]) return 0; if (msg) { *msg = NULL; memprintf(msg, "%s", args[0]); for (i = 1; i <= index; i++) memprintf(msg, "%s %s", *msg, args[i]); memprintf(msg, "'%s' cannot handle unexpected argument '%s'.", *msg, args[index + maxarg + 1]); } if (err_code) *err_code |= ERR_ALERT | ERR_FATAL; return 1; } /* * same as too_many_args_idx with a 0 index */ int too_many_args(int maxarg, char **args, char **msg, int *err_code) { return too_many_args_idx(maxarg, 0, args, msg, err_code); } /* * Report a fatal Alert when there is too much arguments * The index is the current keyword in args * Return 0 if the number of argument is correct, otherwise emit an alert and return 1 * Fill err_code with an ERR_ALERT and an ERR_FATAL */ int alertif_too_many_args_idx(int maxarg, int index, const char *file, int linenum, char **args, int *err_code) { char *kw = NULL; int i; if (!*args[index + maxarg + 1]) return 0; memprintf(&kw, "%s", args[0]); for (i = 1; i <= index; i++) { memprintf(&kw, "%s %s", kw, args[i]); } ha_alert("parsing [%s:%d] : '%s' cannot handle unexpected argument '%s'.\n", file, linenum, kw, args[index + maxarg + 1]); free(kw); *err_code |= ERR_ALERT | ERR_FATAL; return 1; } /* * same as alertif_too_many_args_idx with a 0 index */ int alertif_too_many_args(int maxarg, const char *file, int linenum, char **args, int *err_code) { return alertif_too_many_args_idx(maxarg, 0, file, linenum, args, err_code); } /* Report it if a request ACL condition uses some keywords that are incompatible * with the place where the ACL is used. It returns either 0 or ERR_WARN so that * its result can be or'ed with err_code. Note that may be NULL and then * will be ignored. */ int warnif_cond_conflicts(const struct acl_cond *cond, unsigned int where, const char *file, int line) { const struct acl *acl; const char *kw; if (!cond) return 0; acl = acl_cond_conflicts(cond, where); if (acl) { if (acl->name && *acl->name) ha_warning("parsing [%s:%d] : acl '%s' will never match because it only involves keywords that are incompatible with '%s'\n", file, line, acl->name, sample_ckp_names(where)); else ha_warning("parsing [%s:%d] : anonymous acl will never match because it uses keyword '%s' which is incompatible with '%s'\n", file, line, LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw, sample_ckp_names(where)); return ERR_WARN; } if (!acl_cond_kw_conflicts(cond, where, &acl, &kw)) return 0; if (acl->name && *acl->name) ha_warning("parsing [%s:%d] : acl '%s' involves keywords '%s' which is incompatible with '%s'\n", file, line, acl->name, kw, sample_ckp_names(where)); else ha_warning("parsing [%s:%d] : anonymous acl involves keyword '%s' which is incompatible with '%s'\n", file, line, kw, sample_ckp_names(where)); return ERR_WARN; } /* Parse a string representing a process number or a set of processes. It must * be "all", "odd", "even", a number between 1 and or a range with * two such numbers delimited by a dash ('-'). On success, it returns * 0. otherwise it returns 1 with an error message in . * * Note: this function can also be used to parse a thread number or a set of * threads. */ int parse_process_number(const char *arg, unsigned long *proc, int max, int *autoinc, char **err) { if (autoinc) { *autoinc = 0; if (strncmp(arg, "auto:", 5) == 0) { arg += 5; *autoinc = 1; } } if (strcmp(arg, "all") == 0) *proc |= ~0UL; else if (strcmp(arg, "odd") == 0) *proc |= ~0UL/3UL; /* 0x555....555 */ else if (strcmp(arg, "even") == 0) *proc |= (~0UL/3UL) << 1; /* 0xAAA...AAA */ else { const char *p, *dash = NULL; unsigned int low, high; for (p = arg; *p; p++) { if (*p == '-' && !dash) dash = p; else if (!isdigit((unsigned char)*p)) { memprintf(err, "'%s' is not a valid number/range.", arg); return -1; } } low = high = str2uic(arg); if (dash) high = ((!*(dash+1)) ? max : str2uic(dash + 1)); if (high < low) { unsigned int swap = low; low = high; high = swap; } if (low < 1 || low > max || high > max) { memprintf(err, "'%s' is not a valid number/range." " It supports numbers from 1 to %d.\n", arg, max); return 1; } for (;low <= high; low++) *proc |= 1UL << (low-1); } *proc &= ~0UL >> (LONGBITS - max); return 0; } #ifdef USE_CPU_AFFINITY /* Parse cpu sets. Each CPU set is either a unique number between 0 and * or a range with two such numbers delimited by a dash * ('-'). Multiple CPU numbers or ranges may be specified. On success, it * returns 0. otherwise it returns 1 with an error message in . */ unsigned long parse_cpu_set(const char **args, unsigned long *cpu_set, char **err) { int cur_arg = 0; *cpu_set = 0; while (*args[cur_arg]) { char *dash; unsigned int low, high; if (!isdigit((unsigned char)*args[cur_arg])) { memprintf(err, "'%s' is not a CPU range.\n", args[cur_arg]); return -1; } low = high = str2uic(args[cur_arg]); if ((dash = strchr(args[cur_arg], '-')) != NULL) high = ((!*(dash+1)) ? LONGBITS-1 : str2uic(dash + 1)); if (high < low) { unsigned int swap = low; low = high; high = swap; } if (high >= LONGBITS) { memprintf(err, "supports CPU numbers from 0 to %d.\n", LONGBITS - 1); return 1; } while (low <= high) *cpu_set |= 1UL << low++; cur_arg++; } return 0; } #endif void init_default_instance() { init_new_proxy(&defproxy); defproxy.mode = PR_MODE_TCP; defproxy.state = PR_STNEW; defproxy.maxconn = cfg_maxpconn; defproxy.conn_retries = CONN_RETRIES; defproxy.redispatch_after = 0; defproxy.options = PR_O_REUSE_SAFE; defproxy.max_out_conns = MAX_SRV_LIST; defproxy.defsrv.check.inter = DEF_CHKINTR; defproxy.defsrv.check.fastinter = 0; defproxy.defsrv.check.downinter = 0; defproxy.defsrv.agent.inter = DEF_CHKINTR; defproxy.defsrv.agent.fastinter = 0; defproxy.defsrv.agent.downinter = 0; defproxy.defsrv.check.rise = DEF_RISETIME; defproxy.defsrv.check.fall = DEF_FALLTIME; defproxy.defsrv.agent.rise = DEF_AGENT_RISETIME; defproxy.defsrv.agent.fall = DEF_AGENT_FALLTIME; defproxy.defsrv.check.port = 0; defproxy.defsrv.agent.port = 0; defproxy.defsrv.maxqueue = 0; defproxy.defsrv.minconn = 0; defproxy.defsrv.maxconn = 0; defproxy.defsrv.max_reuse = -1; defproxy.defsrv.max_idle_conns = -1; defproxy.defsrv.pool_purge_delay = 5000; defproxy.defsrv.slowstart = 0; defproxy.defsrv.onerror = DEF_HANA_ONERR; defproxy.defsrv.consecutive_errors_limit = DEF_HANA_ERRLIMIT; defproxy.defsrv.uweight = defproxy.defsrv.iweight = 1; defproxy.email_alert.level = LOG_ALERT; defproxy.load_server_state_from_file = PR_SRV_STATE_FILE_UNSPEC; } /* Allocate and initialize the frontend of a "peers" section found in * file at line with as ID. * Return 0 if succeeded, -1 if not. * Note that this function may be called from "default-server" * or "peer" lines. */ static int init_peers_frontend(const char *file, int linenum, const char *id, struct peers *peers) { struct proxy *p; if (peers->peers_fe) { p = peers->peers_fe; goto out; } p = calloc(1, sizeof *p); if (!p) { ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); return -1; } init_new_proxy(p); peers_setup_frontend(p); p->parent = peers; /* Finally store this frontend. */ peers->peers_fe = p; out: if (id && !p->id) p->id = strdup(id); free(p->conf.file); p->conf.args.file = p->conf.file = strdup(file); if (linenum != -1) p->conf.args.line = p->conf.line = linenum; return 0; } /* Only change ->file, ->line and ->arg struct bind_conf member values * if already present. */ static struct bind_conf *bind_conf_uniq_alloc(struct proxy *p, const char *file, int line, const char *arg, struct xprt_ops *xprt) { struct bind_conf *bind_conf; if (!LIST_ISEMPTY(&p->conf.bind)) { bind_conf = LIST_ELEM((&p->conf.bind)->n, typeof(bind_conf), by_fe); free(bind_conf->file); bind_conf->file = strdup(file); bind_conf->line = line; if (arg) { free(bind_conf->arg); bind_conf->arg = strdup(arg); } } else { bind_conf = bind_conf_alloc(p, file, line, arg, xprt); } return bind_conf; } /* * Allocate a new struct peer parsed at line in file * to be added to . * Returns the new allocated structure if succeeded, NULL if not. */ static struct peer *cfg_peers_add_peer(struct peers *peers, const char *file, int linenum, const char *id, int local) { struct peer *p; p = calloc(1, sizeof *p); if (!p) { ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); return NULL; } /* the peers are linked backwards first */ peers->count++; p->next = peers->remote; peers->remote = p; p->conf.file = strdup(file); p->conf.line = linenum; p->last_change = now.tv_sec; p->xprt = xprt_get(XPRT_RAW); p->sock_init_arg = NULL; HA_SPIN_INIT(&p->lock); if (id) p->id = strdup(id); if (local) { p->local = 1; peers->local = p; } return p; } /* * Parse a line in a , or section. * Returns the error code, 0 if OK, or any combination of : * - ERR_ABORT: must abort ASAP * - ERR_FATAL: we can continue parsing but not start the service * - ERR_WARN: a warning has been emitted * - ERR_ALERT: an alert has been emitted * Only the two first ones can stop processing, the two others are just * indicators. */ int cfg_parse_peers(const char *file, int linenum, char **args, int kwm) { static struct peers *curpeers = NULL; struct peer *newpeer = NULL; const char *err; struct bind_conf *bind_conf; struct listener *l; int err_code = 0; char *errmsg = NULL; static int bind_line, peer_line; if (strcmp(args[0], "bind") == 0 || strcmp(args[0], "default-bind") == 0) { int cur_arg; static int kws_dumped; struct bind_conf *bind_conf; struct bind_kw *kw; cur_arg = 1; if (init_peers_frontend(file, linenum, NULL, curpeers) != 0) { err_code |= ERR_ALERT | ERR_ABORT; goto out; } bind_conf = bind_conf_uniq_alloc(curpeers->peers_fe, file, linenum, NULL, xprt_get(XPRT_RAW)); if (*args[0] == 'b') { struct listener *l; if (peer_line) { ha_alert("parsing [%s:%d] : mixing \"peer\" and \"bind\" line is forbidden\n", file, linenum); err_code |= ERR_ALERT | ERR_FATAL; goto out; } if (!str2listener(args[1], curpeers->peers_fe, bind_conf, file, linenum, &errmsg)) { if (errmsg && *errmsg) { indent_msg(&errmsg, 2); ha_alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg); } else ha_alert("parsing [%s:%d] : '%s %s' : error encountered while parsing listening address %s.\n", file, linenum, args[0], args[1], args[2]); err_code |= ERR_FATAL; goto out; } l = LIST_ELEM(bind_conf->listeners.n, typeof(l), by_bind); l->maxaccept = 1; l->accept = session_accept_fd; l->analysers |= curpeers->peers_fe->fe_req_ana; l->default_target = curpeers->peers_fe->default_target; l->options |= LI_O_UNLIMITED; /* don't make the peers subject to global limits */ global.maxsock++; /* for the listening socket */ bind_line = 1; if (cfg_peers->local) { newpeer = cfg_peers->local; } else { /* This peer is local. * Note that we do not set the peer ID. This latter is initialized * when parsing "peer" or "server" line. */ newpeer = cfg_peers_add_peer(curpeers, file, linenum, NULL, 1); if (!newpeer) { err_code |= ERR_ALERT | ERR_ABORT; goto out; } } newpeer->addr = l->rx.addr; newpeer->proto = protocol_by_family(newpeer->addr.ss_family); cur_arg++; } while (*args[cur_arg] && (kw = bind_find_kw(args[cur_arg]))) { int ret; ret = kw->parse(args, cur_arg, curpeers->peers_fe, bind_conf, &errmsg); err_code |= ret; if (ret) { if (errmsg && *errmsg) { indent_msg(&errmsg, 2); ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg); } else ha_alert("parsing [%s:%d]: error encountered while processing '%s'\n", file, linenum, args[cur_arg]); if (ret & ERR_FATAL) goto out; } cur_arg += 1 + kw->skip; } if (*args[cur_arg] != 0) { char *kws = NULL; if (!kws_dumped) { kws_dumped = 1; bind_dump_kws(&kws); indent_msg(&kws, 4); } ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section.%s%s\n", file, linenum, args[cur_arg], cursection, kws ? " Registered keywords :" : "", kws ? kws: ""); free(kws); err_code |= ERR_ALERT | ERR_FATAL; goto out; } } else if (strcmp(args[0], "default-server") == 0) { if (init_peers_frontend(file, -1, NULL, curpeers) != 0) { err_code |= ERR_ALERT | ERR_ABORT; goto out; } err_code |= parse_server(file, linenum, args, curpeers->peers_fe, NULL, 0, 1, 1); } else if (strcmp(args[0], "log") == 0) { if (init_peers_frontend(file, linenum, NULL, curpeers) != 0) { err_code |= ERR_ALERT | ERR_ABORT; goto out; } if (!parse_logsrv(args, &curpeers->peers_fe->logsrvs, (kwm == KWM_NO), &errmsg)) { ha_alert("parsing [%s:%d] : %s : %s\n", file, linenum, args[0], errmsg); err_code |= ERR_ALERT | ERR_FATAL; goto out; } } else if (strcmp(args[0], "peers") == 0) { /* new peers section */ /* Initialize these static variables when entering a new "peers" section*/ bind_line = peer_line = 0; if (!*args[1]) { ha_alert("parsing [%s:%d] : missing name for peers section.\n", file, linenum); err_code |= ERR_ALERT | ERR_ABORT; goto out; } if (alertif_too_many_args(1, file, linenum, args, &err_code)) goto out; err = invalid_char(args[1]); if (err) { ha_alert("parsing [%s:%d] : character '%c' is not permitted in '%s' name '%s'.\n", file, linenum, *err, args[0], args[1]); err_code |= ERR_ALERT | ERR_ABORT; goto out; } for (curpeers = cfg_peers; curpeers != NULL; curpeers = curpeers->next) { /* * If there are two proxies with the same name only following * combinations are allowed: */ if (strcmp(curpeers->id, args[1]) == 0) { ha_alert("Parsing [%s:%d]: peers section '%s' has the same name as another peers section declared at %s:%d.\n", file, linenum, args[1], curpeers->conf.file, curpeers->conf.line); err_code |= ERR_ALERT | ERR_FATAL; } } if ((curpeers = calloc(1, sizeof(*curpeers))) == NULL) { ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); err_code |= ERR_ALERT | ERR_ABORT; goto out; } curpeers->next = cfg_peers; cfg_peers = curpeers; curpeers->conf.file = strdup(file); curpeers->conf.line = linenum; curpeers->last_change = now.tv_sec; curpeers->id = strdup(args[1]); curpeers->state = PR_STNEW; } else if (strcmp(args[0], "peer") == 0 || strcmp(args[0], "server") == 0) { /* peer or server definition */ int local_peer, peer; peer = *args[0] == 'p'; local_peer = !strcmp(args[1], localpeer); /* The local peer may have already partially been parsed on a "bind" line. */ if (*args[0] == 'p') { if (bind_line) { ha_alert("parsing [%s:%d] : mixing \"peer\" and \"bind\" line is forbidden\n", file, linenum); err_code |= ERR_ALERT | ERR_FATAL; goto out; } peer_line = 1; } if (cfg_peers->local && !cfg_peers->local->id && local_peer) { /* The local peer has already been initialized on a "bind" line. * Let's use it and store its ID. */ newpeer = cfg_peers->local; newpeer->id = strdup(localpeer); } else { if (local_peer && cfg_peers->local) { ha_alert("parsing [%s:%d] : '%s %s' : local peer name already referenced at %s:%d. %s\n", file, linenum, args[0], args[1], curpeers->peers_fe->conf.file, curpeers->peers_fe->conf.line, cfg_peers->local->id); err_code |= ERR_FATAL; goto out; } newpeer = cfg_peers_add_peer(curpeers, file, linenum, args[1], local_peer); if (!newpeer) { err_code |= ERR_ALERT | ERR_ABORT; goto out; } } /* Line number and peer ID are updated only if this peer is the local one. */ if (init_peers_frontend(file, newpeer->local ? linenum: -1, newpeer->local ? newpeer->id : NULL, curpeers) != 0) { err_code |= ERR_ALERT | ERR_ABORT; goto out; } /* This initializes curpeer->peers->peers_fe->srv. * The server address is parsed only if we are parsing a "peer" line, * or if we are parsing a "server" line and the current peer is not the local one. */ err_code |= parse_server(file, linenum, args, curpeers->peers_fe, NULL, peer || !local_peer, 1, 1); if (!curpeers->peers_fe->srv) { /* Remove the newly allocated peer. */ if (newpeer != curpeers->local) { struct peer *p; p = curpeers->remote; curpeers->remote = curpeers->remote->next; free(p->id); free(p); } goto out; } /* If the peer address has just been parsed, let's copy it to * and initializes ->proto. */ if (peer || !local_peer) { newpeer->addr = curpeers->peers_fe->srv->addr; newpeer->proto = protocol_by_family(newpeer->addr.ss_family); } newpeer->xprt = xprt_get(XPRT_RAW); newpeer->sock_init_arg = NULL; HA_SPIN_INIT(&newpeer->lock); newpeer->srv = curpeers->peers_fe->srv; if (!newpeer->local) goto out; /* The lines above are reserved to "peer" lines. */ if (*args[0] == 's') goto out; bind_conf = bind_conf_uniq_alloc(curpeers->peers_fe, file, linenum, args[2], xprt_get(XPRT_RAW)); if (!str2listener(args[2], curpeers->peers_fe, bind_conf, file, linenum, &errmsg)) { if (errmsg && *errmsg) { indent_msg(&errmsg, 2); ha_alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg); } else ha_alert("parsing [%s:%d] : '%s %s' : error encountered while parsing listening address %s.\n", file, linenum, args[0], args[1], args[2]); err_code |= ERR_FATAL; goto out; } l = LIST_ELEM(bind_conf->listeners.n, typeof(l), by_bind); l->maxaccept = 1; l->accept = session_accept_fd; l->analysers |= curpeers->peers_fe->fe_req_ana; l->default_target = curpeers->peers_fe->default_target; l->options |= LI_O_UNLIMITED; /* don't make the peers subject to global limits */ global.maxsock++; /* for the listening socket */ } else if (!strcmp(args[0], "table")) { struct stktable *t, *other; char *id; size_t prefix_len; /* Line number and peer ID are updated only if this peer is the local one. */ if (init_peers_frontend(file, -1, NULL, curpeers) != 0) { err_code |= ERR_ALERT | ERR_ABORT; goto out; } other = stktable_find_by_name(args[1]); if (other) { ha_alert("parsing [%s:%d] : stick-table name '%s' conflicts with table declared in %s '%s' at %s:%d.\n", file, linenum, args[1], other->proxy ? proxy_cap_str(other->proxy->cap) : "peers", other->proxy ? other->id : other->peers.p->id, other->conf.file, other->conf.line); err_code |= ERR_ALERT | ERR_FATAL; goto out; } /* Build the stick-table name, concatenating the "peers" section name * followed by a '/' character and the table name argument. */ chunk_reset(&trash); if (!chunk_strcpy(&trash, curpeers->id)) { ha_alert("parsing [%s:%d]: '%s %s' : stick-table name too long.\n", file, linenum, args[0], args[1]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } prefix_len = trash.data; if (!chunk_memcat(&trash, "/", 1) || !chunk_strcat(&trash, args[1])) { ha_alert("parsing [%s:%d]: '%s %s' : stick-table name too long.\n", file, linenum, args[0], args[1]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } t = calloc(1, sizeof *t); id = strdup(trash.area); if (!t || !id) { ha_alert("parsing [%s:%d]: '%s %s' : memory allocation failed\n", file, linenum, args[0], args[1]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } err_code |= parse_stick_table(file, linenum, args, t, id, id + prefix_len, curpeers); if (err_code & ERR_FATAL) goto out; stktable_store_name(t); t->next = stktables_list; stktables_list = t; } else if (!strcmp(args[0], "disabled")) { /* disables this peers section */ curpeers->state = PR_STSTOPPED; } else if (!strcmp(args[0], "enabled")) { /* enables this peers section (used to revert a disabled default) */ curpeers->state = PR_STNEW; } else if (*args[0] != 0) { ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section\n", file, linenum, args[0], cursection); err_code |= ERR_ALERT | ERR_FATAL; goto out; } out: free(errmsg); return err_code; } /* * Parse a section. * Returns the error code, 0 if OK, or any combination of : * - ERR_ABORT: must abort ASAP * - ERR_FATAL: we can continue parsing but not start the service * - ERR_WARN: a warning has been emitted * - ERR_ALERT: an alert has been emitted * Only the two first ones can stop processing, the two others are just * indicators. */ int cfg_parse_resolvers(const char *file, int linenum, char **args, int kwm) { static struct dns_resolvers *curr_resolvers = NULL; struct dns_nameserver *newnameserver = NULL; const char *err; int err_code = 0; char *errmsg = NULL; if (strcmp(args[0], "resolvers") == 0) { /* new resolvers section */ if (!*args[1]) { ha_alert("parsing [%s:%d] : missing name for resolvers section.\n", file, linenum); err_code |= ERR_ALERT | ERR_ABORT; goto out; } err = invalid_char(args[1]); if (err) { ha_alert("parsing [%s:%d] : character '%c' is not permitted in '%s' name '%s'.\n", file, linenum, *err, args[0], args[1]); err_code |= ERR_ALERT | ERR_ABORT; goto out; } list_for_each_entry(curr_resolvers, &dns_resolvers, list) { /* Error if two resolvers owns the same name */ if (strcmp(curr_resolvers->id, args[1]) == 0) { ha_alert("Parsing [%s:%d]: resolvers '%s' has same name as another resolvers (declared at %s:%d).\n", file, linenum, args[1], curr_resolvers->conf.file, curr_resolvers->conf.line); err_code |= ERR_ALERT | ERR_ABORT; } } if ((curr_resolvers = calloc(1, sizeof(*curr_resolvers))) == NULL) { ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); err_code |= ERR_ALERT | ERR_ABORT; goto out; } /* default values */ LIST_ADDQ(&dns_resolvers, &curr_resolvers->list); curr_resolvers->conf.file = strdup(file); curr_resolvers->conf.line = linenum; curr_resolvers->id = strdup(args[1]); curr_resolvers->query_ids = EB_ROOT; /* default maximum response size */ curr_resolvers->accepted_payload_size = 512; /* default hold period for nx, other, refuse and timeout is 30s */ curr_resolvers->hold.nx = 30000; curr_resolvers->hold.other = 30000; curr_resolvers->hold.refused = 30000; curr_resolvers->hold.timeout = 30000; curr_resolvers->hold.obsolete = 0; /* default hold period for valid is 10s */ curr_resolvers->hold.valid = 10000; curr_resolvers->timeout.resolve = 1000; curr_resolvers->timeout.retry = 1000; curr_resolvers->resolve_retries = 3; curr_resolvers->nb_nameservers = 0; LIST_INIT(&curr_resolvers->nameservers); LIST_INIT(&curr_resolvers->resolutions.curr); LIST_INIT(&curr_resolvers->resolutions.wait); HA_SPIN_INIT(&curr_resolvers->lock); } else if (strcmp(args[0], "nameserver") == 0) { /* nameserver definition */ struct sockaddr_storage *sk; int port1, port2; struct protocol *proto; if (!*args[2]) { ha_alert("parsing [%s:%d] : '%s' expects and [:] as arguments.\n", file, linenum, args[0]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } err = invalid_char(args[1]); if (err) { ha_alert("parsing [%s:%d] : character '%c' is not permitted in server name '%s'.\n", file, linenum, *err, args[1]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } list_for_each_entry(newnameserver, &curr_resolvers->nameservers, list) { /* Error if two resolvers owns the same name */ if (strcmp(newnameserver->id, args[1]) == 0) { ha_alert("Parsing [%s:%d]: nameserver '%s' has same name as another nameserver (declared at %s:%d).\n", file, linenum, args[1], newnameserver->conf.file, newnameserver->conf.line); err_code |= ERR_ALERT | ERR_FATAL; } } if ((newnameserver = calloc(1, sizeof(*newnameserver))) == NULL) { ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); err_code |= ERR_ALERT | ERR_ABORT; goto out; } /* the nameservers are linked backward first */ LIST_ADDQ(&curr_resolvers->nameservers, &newnameserver->list); newnameserver->resolvers = curr_resolvers; newnameserver->conf.file = strdup(file); newnameserver->conf.line = linenum; newnameserver->id = strdup(args[1]); sk = str2sa_range(args[2], NULL, &port1, &port2, &errmsg, NULL, NULL, 1); if (!sk) { ha_alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg); err_code |= ERR_ALERT | ERR_FATAL; goto out; } /* handle nicely the case where "udp@" is forced */ if (sk->ss_family == AF_CUST_UDP4) sk->ss_family = AF_INET; else if (sk->ss_family == AF_CUST_UDP6) sk->ss_family = AF_INET6; proto = protocol_by_family(sk->ss_family); if (!proto) { ha_alert("parsing [%s:%d] : '%s %s' : connect() not supported for this address family.\n", file, linenum, args[0], args[1]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } if (port1 != port2) { ha_alert("parsing [%s:%d] : '%s %s' : port ranges and offsets are not allowed in '%s'\n", file, linenum, args[0], args[1], args[2]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } if (!port1 && !port2) { ha_alert("parsing [%s:%d] : '%s %s' : no UDP port specified\n", file, linenum, args[0], args[1]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } newnameserver->addr = *sk; } else if (strcmp(args[0], "parse-resolv-conf") == 0) { const char *whitespace = "\r\n\t "; char *resolv_line = NULL; int resolv_linenum = 0; FILE *f = NULL; char *address = NULL; struct sockaddr_storage *sk = NULL; struct protocol *proto; int duplicate_name = 0; if ((resolv_line = malloc(sizeof(*resolv_line) * LINESIZE)) == NULL) { ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); err_code |= ERR_ALERT | ERR_FATAL; goto resolv_out; } if ((f = fopen("/etc/resolv.conf", "r")) == NULL) { ha_alert("parsing [%s:%d] : failed to open /etc/resolv.conf.\n", file, linenum); err_code |= ERR_ALERT | ERR_FATAL; goto resolv_out; } sk = calloc(1, sizeof(*sk)); if (sk == NULL) { ha_alert("parsing [/etc/resolv.conf:%d] : out of memory.\n", resolv_linenum); err_code |= ERR_ALERT | ERR_FATAL; goto resolv_out; } while (fgets(resolv_line, LINESIZE, f) != NULL) { resolv_linenum++; if (strncmp(resolv_line, "nameserver", 10) != 0) continue; address = strtok(resolv_line + 10, whitespace); if (address == resolv_line + 10) continue; if (address == NULL) { ha_warning("parsing [/etc/resolv.conf:%d] : nameserver line is missing address.\n", resolv_linenum); err_code |= ERR_WARN; continue; } duplicate_name = 0; list_for_each_entry(newnameserver, &curr_resolvers->nameservers, list) { if (strcmp(newnameserver->id, address) == 0) { ha_warning("Parsing [/etc/resolv.conf:%d] : generated name for /etc/resolv.conf nameserver '%s' conflicts with another nameserver (declared at %s:%d), it appears to be a duplicate and will be excluded.\n", resolv_linenum, address, newnameserver->conf.file, newnameserver->conf.line); err_code |= ERR_WARN; duplicate_name = 1; } } if (duplicate_name) continue; memset(sk, 0, sizeof(*sk)); if (!str2ip2(address, sk, 1)) { ha_warning("parsing [/etc/resolv.conf:%d] : address '%s' could not be recognized, nameserver will be excluded.\n", resolv_linenum, address); err_code |= ERR_WARN; continue; } set_host_port(sk, 53); proto = protocol_by_family(sk->ss_family); if (!proto || !proto->connect) { ha_warning("parsing [/etc/resolv.conf:%d] : '%s' : connect() not supported for this address family.\n", resolv_linenum, address); err_code |= ERR_WARN; continue; } if ((newnameserver = calloc(1, sizeof(*newnameserver))) == NULL) { ha_alert("parsing [/etc/resolv.conf:%d] : out of memory.\n", resolv_linenum); err_code |= ERR_ALERT | ERR_FATAL; goto resolv_out; } newnameserver->conf.file = strdup("/etc/resolv.conf"); if (newnameserver->conf.file == NULL) { ha_alert("parsing [/etc/resolv.conf:%d] : out of memory.\n", resolv_linenum); err_code |= ERR_ALERT | ERR_FATAL; goto resolv_out; } newnameserver->id = strdup(address); if (newnameserver->id == NULL) { ha_alert("parsing [/etc/resolv.conf:%d] : out of memory.\n", resolv_linenum); err_code |= ERR_ALERT | ERR_FATAL; goto resolv_out; } newnameserver->resolvers = curr_resolvers; newnameserver->conf.line = resolv_linenum; newnameserver->addr = *sk; LIST_ADDQ(&curr_resolvers->nameservers, &newnameserver->list); } resolv_out: free(sk); free(resolv_line); if (f != NULL) fclose(f); } else if (strcmp(args[0], "hold") == 0) { /* hold periods */ const char *res; unsigned int time; if (!*args[2]) { ha_alert("parsing [%s:%d] : '%s' expects an and a