/* * 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. * */ /* This is to have crypt() and sched_setaffinity() defined on Linux */ #define _GNU_SOURCE #ifdef USE_LIBCRYPT #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 #ifdef USE_CPU_AFFINITY #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_CPU_AFFINITY #include #endif #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); extern struct proxy *mworker_proxy; /* curproxy is only valid during parsing and will be NULL afterwards. */ struct proxy *curproxy = NULL; char *cursection = NULL; 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 */ int non_global_section_parsed = 0; /* how to handle default paths */ static enum default_path_mode { DEFAULT_PATH_CURRENT = 0, /* "current": paths are relative to CWD (this is the default) */ DEFAULT_PATH_CONFIG, /* "config": paths are relative to config file */ DEFAULT_PATH_PARENT, /* "parent": paths are relative to config file's ".." */ DEFAULT_PATH_ORIGIN, /* "origin": paths are relative to default_path_origin */ } default_path_mode; static char initial_cwd[PATH_MAX]; static char current_cwd[PATH_MAX]; /* 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) { struct protocol *proto; char *next, *dupstr; int port, end; next = dupstr = strdup(str); while (next && *next) { 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, &fd, &proto, NULL, err, (curproxy == global.cli_fe || curproxy == mworker_proxy) ? NULL : global.unix_bind.prefix, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_PORT_MAND | PA_O_PORT_RANGE | PA_O_SOCKET_FD | PA_O_STREAM | PA_O_XPRT); if (!ss2) goto fail; if (ss2->ss_family == AF_CUST_RHTTP_SRV) { /* Check if a previous non reverse HTTP present is * already defined. If DGRAM or STREAM is set, this * indicates that we are currently parsing the second * or more address. */ if (bind_conf->options & (BC_O_USE_SOCK_DGRAM|BC_O_USE_SOCK_STREAM) && !(bind_conf->options & BC_O_REVERSE_HTTP)) { memprintf(err, "Cannot mix reverse HTTP bind with others.\n"); goto fail; } bind_conf->rhttp_srvname = strdup(str + strlen("rhttp@")); if (!bind_conf->rhttp_srvname) { memprintf(err, "Cannot allocate reverse HTTP bind.\n"); goto fail; } bind_conf->options |= BC_O_REVERSE_HTTP; } else if (bind_conf->options & BC_O_REVERSE_HTTP) { /* Standard address mixed with a previous reverse HTTP one. */ memprintf(err, "Cannot mix reverse HTTP bind with others.\n"); goto fail; } /* OK the address looks correct */ if (proto->proto_type == PROTO_TYPE_DGRAM) bind_conf->options |= BC_O_USE_SOCK_DGRAM; else bind_conf->options |= BC_O_USE_SOCK_STREAM; if (proto->xprt_type == PROTO_TYPE_DGRAM) bind_conf->options |= BC_O_USE_XPRT_DGRAM; else bind_conf->options |= BC_O_USE_XPRT_STREAM; if (!create_listeners(bind_conf, ss2, port, end, fd, proto, err)) { memprintf(err, "%s for address '%s'.\n", *err, str); goto fail; } } /* end while(next) */ free(dupstr); return 1; fail: free(dupstr); return 0; } /* * converts to a list of datagram-oriented 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 str2receiver(char *str, struct proxy *curproxy, struct bind_conf *bind_conf, const char *file, int line, char **err) { struct protocol *proto; char *next, *dupstr; int port, end; next = dupstr = strdup(str); while (next && *next) { 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, &fd, &proto, NULL, err, curproxy == global.cli_fe ? NULL : global.unix_bind.prefix, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_PORT_MAND | PA_O_PORT_RANGE | PA_O_SOCKET_FD | PA_O_DGRAM | PA_O_XPRT); if (!ss2) goto fail; /* OK the address looks correct */ if (!create_listeners(bind_conf, ss2, port, end, fd, proto, err)) { memprintf(err, "%s for address '%s'.\n", *err, str); goto fail; } } /* end while(next) */ free(dupstr); return 1; fail: free(dupstr); return 0; } /* * Sends a warning if proxy does not have at least one of the * capabilities in . An optional may be added at the end * of the warning to help the user. Returns 1 if a warning was emitted * or 0 if the condition is valid. */ int warnifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint) { char *msg; switch (cap) { case PR_CAP_BE: msg = "no backend"; break; case PR_CAP_FE: msg = "no frontend"; break; case PR_CAP_BE|PR_CAP_FE: msg = "neither frontend nor backend"; break; default: msg = "not enough"; break; } if (!(proxy->cap & cap)) { ha_warning("parsing [%s:%d] : '%s' ignored because %s '%s' has %s capability.%s\n", file, line, arg, proxy_type_str(proxy), proxy->id, msg, hint ? hint : ""); return 1; } return 0; } /* * Sends an alert if proxy does not have at least one of the * capabilities in . An optional may be added at the end * of the alert to help the user. Returns 1 if an alert was emitted * or 0 if the condition is valid. */ int failifnotcap(struct proxy *proxy, int cap, const char *file, int line, const char *arg, const char *hint) { char *msg; switch (cap) { case PR_CAP_BE: msg = "no backend"; break; case PR_CAP_FE: msg = "no frontend"; break; case PR_CAP_BE|PR_CAP_FE: msg = "neither frontend nor backend"; break; default: msg = "not enough"; break; } if (!(proxy->cap & cap)) { ha_alert("parsing [%s:%d] : '%s' not allowed because %s '%s' has %s capability.%s\n", file, line, arg, proxy_type_str(proxy), proxy->id, msg, hint ? hint : ""); return 1; } 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; } /* Report it if an ACL uses a L6 sample fetch from an HTTP proxy. 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_tcp_http_cond(const struct proxy *px, const struct acl_cond *cond) { if (!cond || px->mode != PR_MODE_HTTP) return 0; if (cond->use & (SMP_USE_L6REQ|SMP_USE_L6RES)) { ha_warning("Proxy '%s': L6 sample fetches ignored on HTTP proxies (declared at %s:%d).\n", px->id, cond->file, cond->line); return ERR_WARN; } return 0; } /* try to find in the word that looks closest to by counting * transitions between letters, digits and other characters. Will return the * best matching word if found, otherwise NULL. An optional array of extra * words to compare may be passed in , but it must then be terminated * by a NULL entry. If unused it may be NULL. */ const char *cfg_find_best_match(const char *word, const struct list *list, int section, const char **extra) { uint8_t word_sig[1024]; // 0..25=letter, 26=digit, 27=other, 28=begin, 29=end uint8_t list_sig[1024]; const struct cfg_kw_list *kwl; int index; const char *best_ptr = NULL; int dist, best_dist = INT_MAX; make_word_fingerprint(word_sig, word); list_for_each_entry(kwl, list, list) { for (index = 0; kwl->kw[index].kw != NULL; index++) { if (kwl->kw[index].section != section) continue; make_word_fingerprint(list_sig, kwl->kw[index].kw); dist = word_fingerprint_distance(word_sig, list_sig); if (dist < best_dist) { best_dist = dist; best_ptr = kwl->kw[index].kw; } } } while (extra && *extra) { make_word_fingerprint(list_sig, *extra); dist = word_fingerprint_distance(word_sig, list_sig); if (dist < best_dist) { best_dist = dist; best_ptr = *extra; } extra++; } if (best_dist > 2 * strlen(word) || (best_ptr && best_dist > 2 * strlen(best_ptr))) best_ptr = NULL; return best_ptr; } /* 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; } /* 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); /* * We keep bind_conf->file and bind_conf->line unchanged * to make them available for error messages */ 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->peers = peers; p->next = peers->remote; peers->remote = p; p->conf.file = strdup(file); p->conf.line = linenum; p->last_change = ns_to_sec(now_ns); 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; static int nb_shards = 0; struct peer *newpeer = NULL; const char *err; struct bind_conf *bind_conf; 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; struct bind_conf *bind_conf; int ret; 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, args[1], xprt_get(XPRT_RAW)); if (!bind_conf) { ha_alert("parsing [%s:%d] : '%s %s' : cannot allocate memory.\n", file, linenum, args[0], args[1]); err_code |= ERR_FATAL; goto out; } bind_conf->maxaccept = 1; bind_conf->accept = session_accept_fd; bind_conf->options |= BC_O_UNLIMITED; /* don't make the peers subject to global limits */ 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 (!LIST_ISEMPTY(&bind_conf->listeners)) { ha_alert("parsing [%s:%d] : One listener per \"peers\" section is authorized but another is already configured at [%s:%d].\n", file, linenum, bind_conf->file, bind_conf->line); err_code |= ERR_FATAL; } 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[1]); err_code |= ERR_FATAL; goto out; } /* Only one listener supported. Compare first listener * against the last one. It must be the same one. */ if (bind_conf->listeners.n != bind_conf->listeners.p) { ha_alert("parsing [%s:%d] : Only one listener per \"peers\" section is authorized. Multiple listening addresses or port range are not supported.\n", file, linenum); err_code |= ERR_ALERT | ERR_FATAL; goto out; } /* * Newly allocated listener is at the end of the list */ l = LIST_ELEM(bind_conf->listeners.p, typeof(l), by_bind); 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 = l->rx.proto; cur_arg++; } ret = bind_parse_args_list(bind_conf, args, cur_arg, cursection, file, linenum); err_code |= ret; if (ret != 0) 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, SRV_PARSE_DEFAULT_SERVER|SRV_PARSE_IN_PEER_SECTION|SRV_PARSE_INITIAL_RESOLVE); } 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_logger(args, &curpeers->peers_fe->loggers, (kwm == KWM_NO), file, linenum, &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 = ns_to_sec(now_ns); curpeers->id = strdup(args[1]); curpeers->disabled = 0; } else if (strcmp(args[0], "peer") == 0 || strcmp(args[0], "server") == 0) { /* peer or server definition */ int local_peer, peer; int parse_addr = 0; peer = *args[0] == 'p'; local_peer = strcmp(args[1], localpeer) == 0; /* 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. */ parse_addr = (peer || !local_peer) ? SRV_PARSE_PARSE_ADDR : 0; err_code |= parse_server(file, linenum, args, curpeers->peers_fe, NULL, SRV_PARSE_IN_PEER_SECTION|parse_addr|SRV_PARSE_INITIAL_RESOLVE); 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 (nb_shards && curpeers->peers_fe->srv->shard > nb_shards) { ha_warning("parsing [%s:%d] : '%s %s' : %d peer shard greater value than %d shards value is ignored.\n", file, linenum, args[0], args[1], curpeers->peers_fe->srv->shard, nb_shards); curpeers->peers_fe->srv->shard = 0; err_code |= ERR_WARN; } if (curpeers->peers_fe->srv->init_addr_methods || curpeers->peers_fe->srv->resolvers_id || curpeers->peers_fe->srv->do_check || curpeers->peers_fe->srv->do_agent) { ha_warning("parsing [%s:%d] : '%s %s' : init_addr, resolvers, check and agent are ignored for peers.\n", file, linenum, args[0], args[1]); err_code |= ERR_WARN; } /* 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_lookup(newpeer->addr.ss_family, PROTO_TYPE_STREAM, 0); } 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 (!bind_conf) { ha_alert("parsing [%s:%d] : '%s %s' : Cannot allocate memory.\n", file, linenum, args[0], args[1]); err_code |= ERR_FATAL; goto out; } bind_conf->maxaccept = 1; bind_conf->accept = session_accept_fd; bind_conf->options |= BC_O_UNLIMITED; /* don't make the peers subject to global limits */ if (!LIST_ISEMPTY(&bind_conf->listeners)) { ha_alert("parsing [%s:%d] : One listener per \"peers\" section is authorized but another is already configured at [%s:%d].\n", file, linenum, bind_conf->file, bind_conf->line); err_code |= ERR_FATAL; } 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; } global.maxsock++; /* for the listening socket */ } else if (strcmp(args[0], "shards") == 0) { char *endptr; if (!*args[1]) { ha_alert("parsing [%s:%d] : '%s' : missing value\n", file, linenum, args[0]); err_code |= ERR_FATAL; goto out; } curpeers->nb_shards = strtol(args[1], &endptr, 10); if (*endptr != '\0') { ha_alert("parsing [%s:%d] : '%s' : expects an integer argument, found '%s'\n", file, linenum, args[0], args[1]); err_code |= ERR_FATAL; goto out; } if (!curpeers->nb_shards) { ha_alert("parsing [%s:%d] : '%s' : expects a strictly positive integer argument\n", file, linenum, args[0]); err_code |= ERR_FATAL; goto out; } nb_shards = curpeers->nb_shards; } else if (strcmp(args[0], "table") == 0) { 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; } /* 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]); free(t); free(id); err_code |= ERR_ALERT | ERR_FATAL; goto out; } other = stktable_find_by_name(trash.area); 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; } err_code |= parse_stick_table(file, linenum, args, t, id, id + prefix_len, curpeers); if (err_code & ERR_FATAL) { free(t); free(id); goto out; } stktable_store_name(t); t->next = stktables_list; stktables_list = t; } else if (strcmp(args[0], "disabled") == 0) { /* disables this peers section */ curpeers->disabled |= PR_FL_DISABLED; } else if (strcmp(args[0], "enabled") == 0) { /* enables this peers section (used to revert a disabled default) */ curpeers->disabled = 0; } else if (*args[0] != 0) { struct peers_kw_list *pkwl; int index; int rc = -1; list_for_each_entry(pkwl, &peers_keywords.list, list) { for (index = 0; pkwl->kw[index].kw != NULL; index++) { if (strcmp(pkwl->kw[index].kw, args[0]) == 0) { rc = pkwl->kw[index].parse(args, curpeers, file, linenum, &errmsg); if (rc < 0) { ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg); err_code |= ERR_ALERT | ERR_FATAL; goto out; } else if (rc > 0) { ha_warning("parsing [%s:%d] : %s\n", file, linenum, errmsg); err_code |= ERR_WARN; goto out; } goto out; } } } 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 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_mailers(const char *file, int linenum, char **args, int kwm) { static struct mailers *curmailers = NULL; struct mailer *newmailer = NULL; const char *err; int err_code = 0; char *errmsg = NULL; if (strcmp(args[0], "mailers") == 0) { /* new mailers section */ if (!*args[1]) { ha_alert("parsing [%s:%d] : missing name for mailers 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; } for (curmailers = mailers; curmailers != NULL; curmailers = curmailers->next) { /* * If there are two proxies with the same name only following * combinations are allowed: */ if (strcmp(curmailers->id, args[1]) == 0) { ha_alert("Parsing [%s:%d]: mailers section '%s' has the same name as another mailers section declared at %s:%d.\n", file, linenum, args[1], curmailers->conf.file, curmailers->conf.line); err_code |= ERR_ALERT | ERR_FATAL; } } if ((curmailers = calloc(1, sizeof(*curmailers))) == NULL) { ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); err_code |= ERR_ALERT | ERR_ABORT; goto out; } curmailers->next = mailers; mailers = curmailers; curmailers->conf.file = strdup(file); curmailers->conf.line = linenum; curmailers->id = strdup(args[1]); curmailers->timeout.mail = DEF_MAILALERTTIME;/* XXX: Would like to Skip to the next alert, if any, ASAP. * But need enough time so that timeouts don't occur * during tcp procssing. For now just us an arbitrary default. */ } else if (strcmp(args[0], "mailer") == 0) { /* mailer 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; } if ((newmailer = calloc(1, sizeof(*newmailer))) == NULL) { ha_alert("parsing [%s:%d] : out of memory.\n", file, linenum); err_code |= ERR_ALERT | ERR_ABORT; goto out; } /* the mailers are linked backwards first */ curmailers->count++; newmailer->next = curmailers->mailer_list; curmailers->mailer_list = newmailer; newmailer->mailers = curmailers; newmailer->conf.file = strdup(file); newmailer->conf.line = linenum; newmailer->id = strdup(args[1]); sk = str2sa_range(args[2], NULL, &port1, &port2, NULL, &proto, NULL, &errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_PORT_MAND | PA_O_STREAM | PA_O_XPRT | PA_O_CONNECT); 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; } if (proto->sock_prot != IPPROTO_TCP) { ha_alert("parsing [%s:%d] : '%s %s' : TCP not supported for this address family.\n", file, linenum, args[0], args[1]); err_code |= ERR_ALERT | ERR_FATAL; goto out; } newmailer->addr = *sk; newmailer->proto = proto; newmailer->xprt = xprt_get(XPRT_RAW); newmailer->sock_init_arg = NULL; } else if (strcmp(args[0], "timeout") == 0) { if (!*args[1]) { ha_alert("parsing [%s:%d] : '%s' expects 'mail' and