/* Libreswan config file parser (confread.c) * Copyright (C) 2001-2002 Mathieu Lafon - Arkoon Network Security * Copyright (C) 2004 Xelerance Corporation * Copyright (C) 2006-2008 Michael Richardson * Copyright (C) 2007 Ken Bantoft * Copyright (C) 2006-2012 Paul Wouters * Copyright (C) 2010 Michael Smith * Copyright (C) 2010 Tuomo Soini * Copyright (C) 2012-2013 Paul Wouters * Copyright (C) 2012 Paul Wouters * Copyright (C) 2012 Avesh Agarwal * Copyright (C) 2012 Antony Antony * Copyright (C) 2013 Florian Weimer * Copyright (C) 2013 David McCullough * Copyright (C) 2013 D. Hugh Redelmeier * * 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. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * */ #include #include #include #include #include #include #include #include "lswalloc.h" #include "ipsecconf/files.h" #include "ipsecconf/confread.h" #include "ipsecconf/starterlog.h" #include "ipsecconf/oeconns.h" #include "ipsecconf/interfaces.h" #include "ipsecconf/keywords.h" #include "ipsecconf/parser.h" /* includes parser.tab.h generated by bison; requires keywords.h */ #ifdef FIPS_CHECK # include "lswconf.h" #endif static char tmp_err[512]; /* * A policy only conn means that we load it, and do the appropriate firewalling * to make sure that no packets get out that this conn would apply to, but we * refuse to negotiate it in any way, either incoming or outgoing. */ #define POLICY_ONLY_CONN(conn) if (conn->options[KBF_AUTO] > \ STARTUP_ONDEMAND) { conn->options[KBF_AUTO] = \ STARTUP_POLICY; } #ifdef DNSSEC # include # include #include /* for inet_ntop */ # include "dnssec.h" #else /* DNSSEC */ struct ub_ctx { }; #endif /* DNSSEC */ /** * Set up hardcoded defaults, from data in programs/pluto/constants.h * * @param cfg starter_config struct * @return void */ void ipsecconf_default_values(struct starter_config *cfg) { if (!cfg) return; zero(cfg); TAILQ_INIT(&cfg->conns); cfg->setup.options[KBF_FRAGICMP] = FALSE; /* see sysctl_ipsec_icmp in ipsec_proc.c */ cfg->setup.options[KBF_HIDETOS] = TRUE; cfg->setup.options[KBF_PLUTORESTARTONCRASH] = TRUE; cfg->setup.options[KBF_PLUTOSTDERRLOGTIME] = FALSE; cfg->setup.options[KBF_UNIQUEIDS] = TRUE; cfg->setup.options[KBF_RETRANSMITS] = TRUE; cfg->setup.options[KBF_PLUTOFORK] = TRUE; /* change in the future */ cfg->setup.options[KBF_PERPEERLOG] = FALSE; cfg->setup.options[KBF_IKEPORT] = IKE_UDP_PORT; cfg->setup.options[KBF_NHELPERS] = -1; /* see also plutomain.c */ cfg->setup.options[KBF_KEEPALIVE] = 0; /* config setup */ cfg->setup.options[KBF_NATIKEPORT] = NAT_IKE_UDP_PORT; #ifdef HAVE_LABELED_IPSEC cfg->setup.options[KBF_SECCTX] = SECCTX; #endif cfg->conn_default.options[KBF_NAT_KEEPALIVE] = TRUE; /* per conn */ cfg->conn_default.options[KBF_TYPE] = KS_TUNNEL; cfg->conn_default.options[KBF_INITIAL_CONTACT] = FALSE; cfg->conn_default.options[KBF_CISCO_UNITY] = FALSE; cfg->conn_default.options[KBF_SEND_VENDORID] = FALSE; cfg->conn_default.options[KBF_REMOTEPEERTYPE] = NON_CISCO; cfg->conn_default.options[KBF_SHA2_TRUNCBUG] = FALSE; cfg->conn_default.options[KBF_IKEPAD] = TRUE; cfg->conn_default.options[KBF_IKEV1_NATT] = natt_both; /*Network Manager support*/ #ifdef HAVE_NM cfg->conn_default.options[KBF_NMCONFIGURED] = FALSE; #endif #ifdef HAVE_LABELED_IPSEC cfg->conn_default.options[KBF_LOOPBACK] = FALSE; cfg->conn_default.options[KBF_LABELED_IPSEC] = FALSE; #endif cfg->conn_default.options[KBF_XAUTHBY] = XAUTHBY_FILE; cfg->conn_default.options[KBF_XAUTHFAIL] = XAUTHFAIL_HARD; cfg->conn_default.policy = POLICY_RSASIG | POLICY_TUNNEL | POLICY_ENCRYPT | POLICY_PFS; cfg->conn_default.policy |= POLICY_IKEV2_ALLOW; /* ikev2=yes */ cfg->conn_default.policy |= POLICY_SAREF_TRACK; /* sareftrack=yes */ cfg->conn_default.policy |= POLICY_IKE_FRAG_ALLOW; /* ike_frag=yes */ cfg->conn_default.options[KBF_IKELIFETIME] = OAKLEY_ISAKMP_SA_LIFETIME_DEFAULT; cfg->conn_default.options[KBF_SALIFETIME] = SA_LIFE_DURATION_DEFAULT; cfg->conn_default.options[KBF_REKEYMARGIN] = SA_REPLACEMENT_MARGIN_DEFAULT; cfg->conn_default.options[KBF_REKEYFUZZ] = SA_REPLACEMENT_FUZZ_DEFAULT; cfg->conn_default.options[KBF_KEYINGTRIES] = SA_REPLACEMENT_RETRIES_DEFAULT; /* now here is a sticker.. we want it on. But pluto has to be smarter first */ cfg->conn_default.options[KBF_OPPOENCRYPT] = FALSE; cfg->conn_default.options[KBF_CONNADDRFAMILY] = AF_INET; cfg->conn_default.left.addr_family = AF_INET; anyaddr(AF_INET, &cfg->conn_default.left.addr); cfg->conn_default.left.nexttype = KH_NOTSET; anyaddr(AF_INET, &cfg->conn_default.left.nexthop); cfg->conn_default.right.addr_family = AF_INET; anyaddr(AF_INET, &cfg->conn_default.right.addr); cfg->conn_default.right.nexttype = KH_NOTSET; anyaddr(AF_INET, &cfg->conn_default.right.nexthop); /* default is NOT to look in DNS */ cfg->conn_default.left.key_from_DNS_on_demand = FALSE; cfg->conn_default.right.key_from_DNS_on_demand = FALSE; cfg->conn_default.options[KBF_AUTO] = STARTUP_IGNORE; cfg->conn_default.state = STATE_LOADED; cfg->ctlbase = clone_str(CTL_FILE, "default base"); } /* format error, and append to string of errors */ static bool error_append(char **perr, const char *fmt, ...) { va_list args; char *nerr; int len; va_start(args, fmt); vsnprintf(tmp_err, sizeof(tmp_err) - 1, fmt, args); va_end(args); len = 1 + strlen(tmp_err) + (*perr != NULL ? strlen(*perr) : 0); nerr = alloc_bytes(len, "error_append len"); nerr[0] = '\0'; if (*perr != NULL) { strcpy(nerr, *perr); /* safe: see allocation above */ pfree(*perr); } strcat(nerr, tmp_err); /* safe: see allocation above */ *perr = nerr; return TRUE; } #define KW_POLICY_FLAG(val, fl) if (conn->options_set[val]) \ { if (conn->options[val]) \ { \ conn->policy |= fl; \ } else { \ conn->policy &= ~fl; \ } } #define KW_POLICY_NEGATIVE_FLAG(val, fl) if (conn->options_set[val]) \ { if (!conn->options[val]) \ { \ conn->policy |= fl; \ } else { \ conn->policy &= ~fl; \ } } #define FREE_LIST(v) { if ((v) != NULL) { free_list(v); (v) = NULL; } } /** * Free the pointer list * * @param list list of pointers * @return void */ static void free_list(char **list) { char **s; for (s = list ; *s; s++) pfreeany(*s); pfree(list); } /** * Create a new list (array) of pointers to strings, NULL-terminated * * @param value string to be broken up at spaces, creating strings for list * @return new_list (pointer to NULL-terminated array of pointers to strings) */ static char **new_list(char *value) { char *val, *b, *e, *end, **nlist; int count; if (value == NULL) return NULL; /* avoid damaging original string */ val = clone_str(value, "new_list value"); end = val + strlen(val); /* count number of items in string */ for (b = val, count = 0; b < end; ) { for (e = b; *e != ' ' && *e != '\0'; e++) ; *e = '\0'; if (e != b) count++; b = e + 1; } if (count == 0) { pfree(val); return NULL; } nlist = (char **)alloc_bytes((count + 1) * sizeof(char *), "new_list nlist"); for (b = val, count = 0; b < end; ) { e = b + strlen(b); if (e != b) nlist[count++] = clone_str(b, "new_list item"); b = e + 1; } nlist[count] = NULL; pfree(val); return nlist; } /** * Load a parsed config * * @param cfg starter_config structure * @param cfgp config_parsed (ie: valid) struct * @param perr pointer to store errors in * @return bool TRUE if unsuccessfull */ static bool load_setup(struct starter_config *cfg, struct config_parsed *cfgp) { bool err = FALSE; struct kw_list *kw; for (kw = cfgp->config_setup; kw; kw = kw->next) { /** * the parser already made sure that only config keywords were used, * but we double check! */ assert(kw->keyword.keydef->validity & kv_config); switch (kw->keyword.keydef->type) { case kt_string: case kt_filename: case kt_dirname: case kt_loose_enum: /* all treated as strings for now */ assert(kw->keyword.keydef->field < sizeof(cfg->setup.strings)); pfreeany(cfg->setup.strings[kw->keyword.keydef-> field]); cfg->setup.strings[kw->keyword.keydef->field] = clone_str(kw->string, "kt_loose_enum kw->string"); cfg->setup.strings_set[kw->keyword.keydef->field] = TRUE; break; case kt_list: case kt_bool: case kt_invertbool: case kt_enum: case kt_number: case kt_time: case kt_percent: /* all treated as a number for now */ assert(kw->keyword.keydef->field < sizeof(cfg->setup.options)); cfg->setup.options[kw->keyword.keydef->field] = kw->number; cfg->setup.options_set[kw->keyword.keydef->field] = TRUE; break; case kt_bitstring: case kt_rsakey: case kt_ipaddr: case kt_subnet: case kt_range: case kt_idtype: err = TRUE; break; case kt_comment: break; case kt_obsolete: starter_log(LOG_LEVEL_INFO, "Warning: ignored obsolete keyword '%s'", kw->keyword.keydef->keyname); break; case kt_obsolete_quiet: starter_log(LOG_LEVEL_DEBUG, "Warning: ignored obsolete keyword '%s'", kw->keyword.keydef->keyname); break; default: /* NEVER HAPPENS */ break; } } /* now process some things with specific values */ /* interfaces has to be chopped up */ if (cfg->setup.interfaces) FREE_LIST(cfg->setup.interfaces); cfg->setup.interfaces = new_list(cfg->setup.strings[KSF_INTERFACES]); return err; } /** * Validate that yes in fact we are one side of the tunnel * * The function checks that IP addresses are valid, nexthops are * present (if needed) as well as policies, and sets the leftID * from the left= if it isn't set. * * @param conn_st a connection definition * @param end a connection end * @param leftright const char * "left" or "right" * @param perr pointer to char containing error value * @return bool TRUE if failed */ static bool validate_end(struct ub_ctx *dnsctx, struct starter_conn *conn_st, struct starter_end *end, const char *leftright, bool resolvip UNUSED, err_t *perr) { err_t er = NULL; char *err_str = NULL; int family = conn_st->options[KBF_CONNADDRFAMILY]; bool err = FALSE; # define ERR_FOUND(args ...) do err |= error_append(&err_str, ## args); while (0) if (!end->options_set[KNCF_IP]) conn_st->state = STATE_INCOMPLETE; end->addrtype = end->options[KNCF_IP]; end->addr_family = family; /* validate the KSCF_IP/KNCF_IP */ switch (end->addrtype) { case KH_ANY: anyaddr(family, &(end->addr)); break; case KH_IFACE: /* generally, this doesn't show up at this stage */ starter_log(LOG_LEVEL_DEBUG, "starter: case KH_IFACE empty"); break; case KH_IPADDR: assert(end->strings[KSCF_IP] != NULL); if (end->strings[KSCF_IP][0] == '%') { pfree(end->iface); end->iface = clone_str(end->strings[KSCF_IP] + 1, "KH_IPADDR end->iface"); if (!starter_iface_find(end->iface, family, &end->addr, &end->nexthop)) conn_st->state = STATE_INVALID; /* not numeric, so set the type to the iface type */ end->addrtype = KH_IFACE; break; } er = ttoaddr_num(end->strings[KNCF_IP], 0, family, &(end->addr)); if (er != NULL) { /* not numeric, so set the type to the string type */ end->addrtype = KH_IPHOSTNAME; } if (end->id == NULL) { ipstr_buf b; end->id = clone_str(ipstr(&end->addr, &b), "end if"); } break; case KH_OPPO: conn_st->policy |= POLICY_OPPORTUNISTIC; break; case KH_OPPOGROUP: conn_st->policy |= POLICY_OPPORTUNISTIC | POLICY_GROUP; break; case KH_GROUP: conn_st->policy |= POLICY_GROUP; break; case KH_IPHOSTNAME: /* generally, this doesn't show up at this stage */ starter_log(LOG_LEVEL_DEBUG, "starter: case KH_IPHOSTNAME empty"); break; case KH_DEFAULTROUTE: starter_log(LOG_LEVEL_DEBUG, "starter: case KH_DEFAULTROUTE: empty"); break; case KH_NOTSET: starter_log(LOG_LEVEL_DEBUG, "starter: case KH_NOTSET: empty"); break; } /* validate the KSCF_SUBNET */ if (end->strings_set[KSCF_SUBNET]) { char *value = end->strings[KSCF_SUBNET]; if (end->strings_set[KSCF_ADDRESSPOOL]) { ERR_FOUND("cannot specify both %ssubnet= and %saddresspool=", leftright, leftright); } if (startswith(value, "vhost:") || startswith(value, "vnet:")) { er = NULL; end->virt = clone_str(value, "validate_end item"); } else { end->has_client = TRUE; er = ttosubnet(value, 0, family, &(end->subnet)); } if (er != NULL) ERR_FOUND("bad subnet %ssubnet=%s [%s]", leftright, value, er); } /* set nexthop address to something consistent, by default */ anyaddr(family, &end->nexthop); anyaddr(addrtypeof(&end->addr), &end->nexthop); /* validate the KSCF_NEXTHOP */ if (end->strings_set[KSCF_NEXTHOP]) { char *value = end->strings[KSCF_NEXTHOP]; if (strcaseeq(value, "%defaultroute")) { end->nexttype = KH_DEFAULTROUTE; } else { if (tnatoaddr(value, strlen(value), AF_INET, &(end->nexthop)) != NULL && tnatoaddr(value, strlen(value), AF_INET6, &(end->nexthop)) != NULL) { #ifdef DNSSEC starter_log(LOG_LEVEL_DEBUG, "Calling unbound_resolve() for %snexthop value\n", leftright); if (!unbound_resolve(dnsctx, value, strlen(value), AF_INET, &(end->nexthop)) && !unbound_resolve(dnsctx, value, strlen(value), AF_INET6, &(end->nexthop))) ERR_FOUND( "bad value for %snexthop=%s\n", leftright, value); #else er = ttoaddr(value, 0, family, &(end->nexthop)); if (er != NULL) ERR_FOUND( "bad value for %snexthop=%s [%s]", leftright, value, er); #endif } end->nexttype = KH_IPADDR; } } else { #if 0 if (conn_st->policy & POLICY_OPPORTUNISTIC) end->nexttype = KH_DEFAULTROUTE; #endif anyaddr(family, &end->nexthop); if (end->addrtype == KH_DEFAULTROUTE) { end->nexttype = KH_DEFAULTROUTE; } } /* validate the KSCF_ID */ if (end->strings_set[KSCF_ID]) { char *value = end->strings[KSCF_ID]; pfreeany(end->id); end->id = clone_str(value, "end->id"); } if (end->options_set[KSCF_RSAKEY1]) { end->rsakey1_type = end->options[KSCF_RSAKEY1]; end->rsakey2_type = end->options[KSCF_RSAKEY2]; switch (end->options[KSCF_RSAKEY1]) { case PUBKEY_DNS: case PUBKEY_DNSONDEMAND: end->key_from_DNS_on_demand = TRUE; break; default: end->key_from_DNS_on_demand = FALSE; /* validate the KSCF_RSAKEY1/RSAKEY2 */ if (end->strings[KSCF_RSAKEY1] != NULL) { char *value = end->strings[KSCF_RSAKEY1]; pfreeany(end->rsakey1); end->rsakey1 = (unsigned char *)clone_str(value,"end->rsakey1"); } if (end->strings[KSCF_RSAKEY2] != NULL) { char *value = end->strings[KSCF_RSAKEY2]; pfreeany(end->rsakey2); end->rsakey2 = (unsigned char *)clone_str(value,"end->rsakey2"); } } } /* validate the KSCF_SOURCEIP, if any, and if set, * set the subnet to same value, if not set. */ if (end->strings_set[KSCF_SOURCEIP]) { char *value = end->strings[KSCF_SOURCEIP]; if (tnatoaddr(value, strlen(value), AF_INET, &(end->sourceip)) != NULL && tnatoaddr(value, strlen(value), AF_INET6, &(end->sourceip)) != NULL) { #ifdef DNSSEC starter_log(LOG_LEVEL_DEBUG, "Calling unbound_resolve() for %ssourceip value\n", leftright); if (!unbound_resolve(dnsctx, value, strlen(value), AF_INET, &(end->sourceip)) && !unbound_resolve(dnsctx, value, strlen(value), AF_INET6, &(end->sourceip))) ERR_FOUND("bad value for %ssourceip=%s\n", leftright, value); #else er = ttoaddr(value, 0, family, &(end->sourceip)); if (er) ERR_FOUND("bad addr %ssourceip=%s [%s]", leftright, value, er); #endif } else { er = tnatoaddr(value, 0, family, &(end->sourceip)); if (er) ERR_FOUND("bad numerical addr %ssourceip=%s [%s]", leftright, value, er); } if (!end->has_client) { starter_log(LOG_LEVEL_INFO, "%ssourceip= used but not %ssubnet= defined, defaulting %ssubnet to %s\n", leftright, leftright, leftright, value); er = addrtosubnet(&end->sourceip, &end->subnet); if (er) { ERR_FOUND( "attempt to default %ssubnet from %s failed: %s", leftright, value, er); } end->has_client = TRUE; end->has_client_wildcard = FALSE; } } /* copy certificate path name */ if (end->strings_set[KSCF_CERT]) end->cert = clone_str(end->strings[KSCF_CERT], "KSCF_CERT"); if (end->strings_set[KSCF_CA]) end->ca = clone_str(end->strings[KSCF_CA], "KSCF_CA"); if (end->strings_set[KSCF_UPDOWN]) end->updown = clone_str(end->strings[KSCF_UPDOWN], "KSCF_UPDOWN"); if (end->strings_set[KSCF_PROTOPORT]) { err_t ugh; char *value = end->strings[KSCF_PROTOPORT]; ugh = ttoprotoport(value, 0, &end->protocol, &end->port, &end->has_port_wildcard); if (ugh) ERR_FOUND("bad %sprotoport=%s [%s]", leftright, value, ugh); } if (end->strings_set[KSCF_ADDRESSPOOL]) { char *addresspool = end->strings[KSCF_ADDRESSPOOL]; if (end->strings_set[KSCF_SUBNET]) ERR_FOUND("cannot specify both %ssubnet= and %saddresspool=", leftright, leftright); starter_log(LOG_LEVEL_DEBUG, "connection's %saddresspool set to: %s", leftright, end->strings[KSCF_ADDRESSPOOL] ); er = ttorange(addresspool, 0, AF_INET, &end->pool_range, TRUE); if (er != NULL) ERR_FOUND("bad %saddresspool=%s [%s]", leftright, addresspool, er); } if (end->options_set[KNCF_XAUTHSERVER] || end->options_set[KNCF_XAUTHCLIENT]) conn_st->policy |= POLICY_XAUTH; /* KSCF_SUBNETWITHIN --- not sure what to do with it. KSCF_ESPENCKEY --- todo (manual keying) KSCF_ESPAUTHKEY --- todo (manual keying) KSCF_SOURCEIP = 16, KSCF_MAX = 19 */ if (err) *perr = err_str; return err; # undef ERR_FOUND } /** * Take keywords from ipsec.conf syntax and load into a conn struct * * * @param conn a connection definition * @param sl a section_list * @param assigned_value is set to either k_set, or k_default. * k_default is used when we are loading a conn that should be * considered to be a "default" value, and that replacing this * value is considered acceptable. * @return bool TRUE if unsuccessfull */ static bool translate_conn(struct starter_conn *conn, struct section_list *sl, enum keyword_set assigned_value, err_t *error) { bool err = FALSE; struct kw_list *kw; for (kw = sl->kw; kw; kw = kw->next) { ksf *the_strings = &conn->strings; str_set *set_strings = &conn->strings_set; knf *the_options = &conn->options; int_set *set_options = &conn->options_set; unsigned int field; if ((kw->keyword.keydef->validity & kv_conn) == 0) { /* this isn't valid in a conn! */ *error = (const char *)tmp_err; snprintf(tmp_err, sizeof(tmp_err), "keyword '%s' is not valid in a conn (%s)\n", kw->keyword.keydef->keyname, sl->name); starter_log(LOG_LEVEL_INFO, "%s", tmp_err); continue; } if (kw->keyword.keydef->validity & kv_leftright) { if (kw->keyword.keyleft) { the_strings = &conn->left.strings; the_options = &conn->left.options; set_strings = &conn->left.strings_set; set_options = &conn->left.options_set; } else { the_strings = &conn->right.strings; the_options = &conn->right.options; set_strings = &conn->right.strings_set; set_options = &conn->right.options_set; } } field = kw->keyword.keydef->field; #ifdef PARSER_TYPE_DEBUG starter_log(LOG_LEVEL_DEBUG, "#analyzing %s[%d] kwtype=%d\n", kw->keyword.keydef->keyname, field, kw->keyword.keydef->type); #endif assert(kw->keyword.keydef != NULL); switch (kw->keyword.keydef->type) { case kt_string: case kt_filename: case kt_dirname: case kt_bitstring: case kt_ipaddr: case kt_range: case kt_subnet: case kt_idtype: /* all treated as strings for now */ assert(kw->keyword.keydef->field < KEY_STRINGS_MAX); if ((*set_strings)[field] == k_set) { *error = tmp_err; snprintf(tmp_err, sizeof(tmp_err), "duplicate key '%s' in conn %s while processing def %s", kw->keyword.keydef->keyname, conn->name, sl->name); starter_log(LOG_LEVEL_INFO, "%s", tmp_err); if (kw->keyword.string == NULL || (*the_strings)[field] == NULL || !streq(kw->keyword.string, (*the_strings)[field])) { err = TRUE; break; } } pfreeany((*the_strings)[field]); if (kw->string == NULL) { *error = tmp_err; snprintf(tmp_err, sizeof(tmp_err), "Invalid %s value", kw->keyword.keydef->keyname); err = TRUE; break; } (*the_strings)[field] = clone_str(kw->string,"kt_idtype kw->string"); (*set_strings)[field] = assigned_value; break; case kt_appendstring: case kt_appendlist: /* implicitly, this field can have multiple values */ assert(kw->keyword.keydef->field < KEY_STRINGS_MAX); if ((*the_strings)[field] == NULL) { (*the_strings)[field] = clone_str(kw->string, "kt_appendlist kw->string"); } else { char *s = (*the_strings)[field]; size_t old_len = strlen(s); /* excludes '\0' */ size_t new_len = strlen(kw->string); char *n; n = alloc_bytes(old_len + 1 + new_len + 1, "kt_appendlist"); memcpy(n, s, old_len); n[old_len] = ' '; memcpy(n + old_len + 1, kw->string, new_len + 1); /* includes '\0' */ (*the_strings)[field] = n; pfree(s); } (*set_strings)[field] = TRUE; break; case kt_rsakey: case kt_loose_enum: assert(field < KEY_STRINGS_MAX); assert(field < KEY_NUMERIC_MAX); if ((*set_options)[field] == k_set) { *error = tmp_err; snprintf(tmp_err, sizeof(tmp_err), "duplicate key '%s' in conn %s while processing def %s", kw->keyword.keydef->keyname, conn->name, sl->name); starter_log(LOG_LEVEL_INFO, "%s", tmp_err); /* only fatal if we try to change values */ if ((*the_options)[field] != (int)kw->number || !((*the_options)[field] == LOOSE_ENUM_OTHER && kw->number == LOOSE_ENUM_OTHER && kw->keyword.string != NULL && (*the_strings)[field] != NULL && streq(kw->keyword.string, (*the_strings)[field]))) { err = TRUE; break; } } (*the_options)[field] = kw->number; if (kw->number == LOOSE_ENUM_OTHER) { assert(kw->keyword.string != NULL); pfreeany((*the_strings)[field]); (*the_strings)[field] = clone_str( kw->keyword.string, "kt_loose_enum kw->keyword.string"); } (*set_options)[field] = assigned_value; break; case kt_list: case kt_bool: case kt_invertbool: case kt_enum: case kt_number: case kt_time: case kt_percent: /* all treated as a number for now */ assert(field < KEY_NUMERIC_MAX); if ((*set_options)[field] == k_set) { *error = tmp_err; snprintf(tmp_err, sizeof(tmp_err), "duplicate key '%s' in conn %s while processing def %s", kw->keyword.keydef->keyname, conn->name, sl->name); starter_log(LOG_LEVEL_INFO, "%s", tmp_err); if ((*the_options)[field] != (int)kw->number) { err = TRUE; break; } } #if 0 starter_log(LOG_LEVEL_DEBUG, "#setting %s[%d]=%u\n", kw->keyword.keydef->keyname, field, kw->number); #endif (*the_options)[field] = kw->number; (*set_options)[field] = assigned_value; break; case kt_comment: break; case kt_obsolete: starter_log(LOG_LEVEL_INFO, "Warning: obsolete keyword '%s' ignored\n", kw->keyword.keydef->keyname); break; case kt_obsolete_quiet: starter_log(LOG_LEVEL_DEBUG, "Warning: obsolete keyword '%s' ignored\n", kw->keyword.keydef->keyname); break; } } return err; } static void move_comment_list(struct starter_comments_list *to, struct starter_comments_list *from) { struct starter_comments *sc, *scnext; for (sc = from->tqh_first; sc != NULL; sc = scnext) { scnext = sc->link.tqe_next; TAILQ_REMOVE(from, sc, link); TAILQ_INSERT_TAIL(to, sc, link); } } static bool load_conn_basic(struct starter_conn *conn, struct section_list *sl, enum keyword_set assigned_value, err_t *perr) { /* turn all of the keyword/value pairs into options/strings in left/right */ return translate_conn(conn, sl, assigned_value, perr); } static bool load_conn(struct ub_ctx *dnsctx, struct starter_conn *conn, struct config_parsed *cfgp, struct section_list *sl, bool alsoprocessing, bool defaultconn, bool resolvip, err_t *perr) { bool err = FALSE; err |= load_conn_basic(conn, sl, defaultconn ? k_default : k_set, perr); move_comment_list(&conn->comments, &sl->comments); if (err) return err; if (conn->strings[KSF_ALSO] != NULL && !alsoprocessing) { starter_log(LOG_LEVEL_INFO, "also= is not valid in section '%s'", sl->name); return TRUE; /* error */ } /* now, process the also's */ if (conn->alsos) FREE_LIST(conn->alsos); conn->alsos = new_list(conn->strings[KSF_ALSO]); if (alsoprocessing && conn->alsos != NULL) { struct section_list *sl1; /* note: for the duration of this loop body * conn->alsos is migrated to local variable alsos. */ char **alsos = conn->alsos; int alsosize; int alsoplace; conn->alsos = NULL; /* reset all of the "beenhere" flags */ for (sl1 = cfgp->sections.tqh_first; sl1 != NULL; sl1 = sl1->link.tqe_next) sl1->beenhere = FALSE; sl->beenhere = TRUE; /* count them */ for (alsosize = 0; alsos[alsosize] != NULL; alsosize++) ; alsoplace = 0; while (alsoplace < alsosize && alsos[alsoplace] != NULL && alsoplace < ALSO_LIMIT) { /* * for each also= listed, go find this section's keyword list, and * load it as well. This may extend the also= list (and the end), * which we handle by zeroing the also list, and adding to it after * checking for duplicates. */ for (sl1 = cfgp->sections.tqh_first; sl1 != NULL && !streq(alsos[alsoplace], sl1->name); sl1 = sl1->link.tqe_next) ; starter_log(LOG_LEVEL_DEBUG, "\twhile loading conn '%s' also including '%s'", conn->name, alsos[alsoplace]); /* * if we found something that matches by name, and we haven't be * there, then process it. */ if (sl1 && !sl1->beenhere) { conn->strings_set[KSF_ALSO] = FALSE; pfreeany(conn->strings[KSF_ALSO]); conn->strings[KSF_ALSO] = NULL; sl1->beenhere = TRUE; /* translate things, but do not replace earlier settings!*/ err |= translate_conn(conn, sl1, k_set, perr); if (conn->strings[KSF_ALSO] != NULL) { /* now, check out the KSF_ALSO, and extend list if we need to */ char **newalsos = new_list( conn->strings[KSF_ALSO]); if (newalsos != NULL) { char **ra; int newalsoplace; /* count them */ for (newalsoplace = 0; newalsos[newalsoplace] != NULL; newalsoplace++) ; /* extend conn->alss */ ra = alloc_bytes((alsosize + newalsoplace + 1) * sizeof(char *), "conn->alsos"); memcpy(ra, alsos, alsosize * sizeof(char *)); pfree(alsos); alsos = ra; for (newalsoplace = 0; newalsos[newalsoplace] != NULL; newalsoplace++) { assert(conn != NULL); assert(conn->name != NULL); starter_log( LOG_LEVEL_DEBUG, "\twhile processing section '%s' added also=%s", sl1->name, newalsos[newalsoplace]); alsos[alsosize++] = clone_str(newalsos[newalsoplace], "alsos"); } alsos[alsosize] = NULL; } FREE_LIST(newalsos); } } alsoplace++; } /* migrate alsos back to conn->alsos */ conn->alsos = alsos; if (alsoplace >= ALSO_LIMIT) { starter_log(LOG_LEVEL_INFO, "while loading conn '%s', too many also= used at section %s. Limit is %d", conn->name, alsos[alsoplace], ALSO_LIMIT); return TRUE; /* error */ } } #ifdef PARSER_TYPE_DEBUG /* translate strings/numbers into conn items */ starter_log(LOG_LEVEL_DEBUG, "#checking options_set[KBF_TYPE,%d]=%d %d\n", KBF_TYPE, conn->options_set[KBF_TYPE], conn->options[KBF_TYPE]); #endif if (conn->options_set[KBF_TYPE]) { switch ((enum keyword_satype)conn->options[KBF_TYPE]) { case KS_TUNNEL: conn->policy |= POLICY_TUNNEL; conn->policy &= ~POLICY_SHUNT_MASK; break; case KS_TRANSPORT: conn->policy &= ~POLICY_TUNNEL; conn->policy &= ~POLICY_SHUNT_MASK; break; case KS_PASSTHROUGH: conn->policy &= ~(POLICY_ENCRYPT | POLICY_AUTHENTICATE | POLICY_TUNNEL | POLICY_RSASIG); conn->policy &= ~POLICY_SHUNT_MASK; conn->policy |= POLICY_SHUNT_PASS; break; case KS_DROP: conn->policy &= ~(POLICY_ENCRYPT | POLICY_AUTHENTICATE | POLICY_TUNNEL | POLICY_RSASIG); conn->policy &= ~POLICY_SHUNT_MASK; conn->policy |= POLICY_SHUNT_DROP; break; case KS_REJECT: conn->policy &= ~(POLICY_ENCRYPT | POLICY_AUTHENTICATE | POLICY_TUNNEL | POLICY_RSASIG); conn->policy &= ~POLICY_SHUNT_MASK; conn->policy |= POLICY_SHUNT_REJECT; break; } } KW_POLICY_FLAG(KBF_COMPRESS, POLICY_COMPRESS); KW_POLICY_FLAG(KBF_PFS, POLICY_PFS); /* reset authby flags */ if (conn->options_set[KBF_AUTHBY]) { conn->policy &= ~(POLICY_ID_AUTH_MASK); #ifdef FIPS_CHECK if (Pluto_IsFIPS()) { if ((conn->options[KBF_AUTHBY] & POLICY_PSK) == POLICY_PSK) { starter_log(LOG_LEVEL_INFO, "while loading conn '%s', PSK not allowed in FIPS mode with NSS", conn->name); return TRUE; /* error */ } } #endif conn->policy |= conn->options[KBF_AUTHBY]; #ifdef STARTER_POLICY_DEBUG starter_log(LOG_LEVEL_DEBUG, "%s: setting conn->policy=%08x (%08x)\n", conn->name, (unsigned int)conn->policy, conn->options[KBF_AUTHBY]); #endif } KW_POLICY_NEGATIVE_FLAG(KBF_IKEPAD, POLICY_NO_IKEPAD); KW_POLICY_NEGATIVE_FLAG(KBF_REKEY, POLICY_DONT_REKEY); KW_POLICY_FLAG(KBF_AGGRMODE, POLICY_AGGRESSIVE); KW_POLICY_FLAG(KBF_MODECONFIGPULL, POLICY_MODECFG_PULL); KW_POLICY_FLAG(KBF_OVERLAPIP, POLICY_OVERLAPIP); KW_POLICY_FLAG(KBF_IKEv2_ALLOW_NARROWING, POLICY_IKEV2_ALLOW_NARROWING); if (conn->strings_set[KSF_ESP]) conn->esp = clone_str(conn->strings[KSF_ESP],"KSF_ESP"); #ifdef HAVE_LABELED_IPSEC if (conn->strings_set[KSF_POLICY_LABEL]) conn->policy_label = clone_str(conn->strings[KSF_POLICY_LABEL],"KSF_POLICY_LABEL"); if (conn->policy_label != NULL) starter_log(LOG_LEVEL_DEBUG, "connection's policy label: %s", conn->policy_label); #endif if (conn->strings_set[KSF_IKE]) conn->ike = clone_str(conn->strings[KSF_IKE],"KSF_IKE"); if (conn->strings_set[KSF_MODECFGDNS1]) { conn->modecfg_dns1 = clone_str(conn->strings[KSF_MODECFGDNS1],"KSF_MODECFGDNS1"); } if (conn->strings_set[KSF_MODECFGDNS2]) { conn->modecfg_dns2 = clone_str(conn->strings[KSF_MODECFGDNS2], "KSF_MODECFGDNS2"); } if (conn->strings_set[KSF_MODECFGDOMAIN]) { conn->modecfg_domain = clone_str(conn->strings[KSF_MODECFGDOMAIN],"KSF_MODECFGDOMAIN"); } if (conn->strings_set[KSF_MODECFGBANNER]) { conn->modecfg_banner = clone_str(conn->strings[KSF_MODECFGBANNER],"KSF_MODECFGBANNER"); } if (conn->strings_set[KSF_CONNALIAS]) conn->connalias = clone_str(conn->strings[KSF_CONNALIAS],"KSF_CONNALIAS"); if (conn->options_set[KBF_PHASE2]) { conn->policy &= ~(POLICY_AUTHENTICATE | POLICY_ENCRYPT); conn->policy |= conn->options[KBF_PHASE2]; } if (conn->options_set[KBF_IKEv2]) { switch (conn->options[KBF_IKEv2]) { case fo_never: conn->policy &= ~POLICY_IKEV2_ALLOW; break; case fo_permit: /* this is the default for now */ conn->policy |= POLICY_IKEV2_ALLOW; break; case fo_propose: conn->policy |= POLICY_IKEV2_ALLOW | POLICY_IKEV2_PROPOSE; break; case fo_insist: conn->policy |= POLICY_IKEV1_DISABLE; conn->policy |= POLICY_IKEV2_ALLOW | POLICY_IKEV2_PROPOSE; break; } } if (conn->options_set[KBF_IKE_FRAG]) { switch (conn->options[KBF_IKE_FRAG]) { case ynf_no: conn->policy &= ~POLICY_IKE_FRAG_ALLOW; conn->policy &= ~POLICY_IKE_FRAG_FORCE; break; case ynf_yes: /* this is the default */ conn->policy |= POLICY_IKE_FRAG_ALLOW; break; case ynf_force: conn->policy |= POLICY_IKE_FRAG_ALLOW | POLICY_IKE_FRAG_FORCE; break; } } if (conn->options_set[KBF_SAREFTRACK]) { switch (conn->options[KBF_SAREFTRACK]) { case sat_yes: /* this is the default */ conn->policy |= POLICY_SAREF_TRACK; break; case sat_conntrack: conn->policy |= POLICY_SAREF_TRACK | POLICY_SAREF_TRACK_CONNTRACK; break; case sat_no: conn->policy &= ~POLICY_SAREF_TRACK; conn->policy &= ~POLICY_SAREF_TRACK_CONNTRACK; break; } } err |= validate_end(dnsctx, conn, &conn->left, "left", resolvip, perr); err |= validate_end(dnsctx, conn, &conn->right, "right", resolvip, perr); /* * TODO: * verify both ends are using the same inet family, if one end * is "%any" or "%defaultroute", then perhaps adjust it. * ensource this for left,leftnexthop,right,rightnexthop * Ideally, phase out connaddrfamily= which now wrongly assumes * left,leftnextop,leftsubnet are the same inet family * Currently, these tests are implicitely done, and wrongly * in case of 6in4 and 4in6 tunnels */ if (conn->options_set[KBF_AUTO]) conn->desired_state = conn->options[KBF_AUTO]; return err; } static void conn_default(struct starter_conn *conn, struct starter_conn *def) { int i; /* structure copy to start */ *conn = *def; /* unlink it */ zero(&conn->link); conn->left.iface = clone_str(def->left.iface, "conn default left iface"); conn->left.id = clone_str(def->left.id, "conn default leftid"); conn->left.rsakey1 = clone_thing(def->left.rsakey1, "conn default left rsakey1"); conn->left.rsakey2 = clone_thing(def->left.rsakey2, "conn default left rsakey2"); conn->right.iface = clone_str(def->right.iface, "conn default right iface"); conn->right.id = clone_str(def->right.id, "conn default rightid"); conn->right.rsakey1 = clone_thing(def->right.rsakey1, "conn default right rsakey1"); conn->right.rsakey2 = clone_thing(def->right.rsakey2, "conn default right rsakey2"); for (i = 0; i < KSCF_MAX; i++) { conn->left.strings[i] = clone_str(def->left.strings[i], "conn default left item"); conn->right.strings[i] = clone_str(def->right.strings[i], "conn default right item"); } for (i = 0; i < KNCF_MAX; i++) { conn->left.options[i] = def->left.options[i]; conn->right.options[i] = def->right.options[i]; } for (i = 0; i < KSF_MAX; i++) conn->strings[i] = clone_str(def->strings[i], "conn default string item"); for (i = 0; i < KBF_MAX; i++) conn->options[i] = def->options[i]; conn->esp = clone_str(def->esp, "conn default esp"); conn->ike = clone_str(def->ike, "conn default ike"); conn->modecfg_dns1 = clone_str(def->modecfg_dns1, "conn default dns1"); conn->modecfg_dns2 = clone_str(def->modecfg_dns2, "conn default dns2"); conn->modecfg_domain = clone_str(def->modecfg_domain, "conn default domain"); conn->modecfg_banner = clone_str(def->modecfg_banner, "conn default banner"); #ifdef HAVE_LABELED_IPSEC conn->policy_label = clone_str(def->policy_label, "conn default policy_label"); #endif conn->policy = def->policy; } struct starter_conn *alloc_add_conn(struct starter_config *cfg, char *name) { struct starter_conn *conn; conn = (struct starter_conn *)alloc_bytes(sizeof(struct starter_conn),"add_conn starter_conn"); zero(conn); conn_default(conn, &cfg->conn_default); conn->name = clone_str(name, "add conn name"); conn->desired_state = STARTUP_IGNORE; conn->state = STATE_FAILED; TAILQ_INIT(&conn->comments); TAILQ_INSERT_TAIL(&cfg->conns, conn, link); return conn; } static bool init_load_conn(struct ub_ctx *dnsctx, struct starter_config *cfg, struct config_parsed *cfgp, struct section_list *sconn, bool defaultconn, bool resolvip, err_t *perr) { bool connerr; struct starter_conn *conn; starter_log(LOG_LEVEL_DEBUG, "Loading conn %s", sconn->name); conn = alloc_add_conn(cfg, sconn->name); connerr = load_conn(dnsctx, conn, cfgp, sconn, TRUE, defaultconn, resolvip, perr); if (connerr) { starter_log(LOG_LEVEL_INFO, "while loading '%s': %s\n", sconn->name, *perr); } else { conn->state = STATE_LOADED; } return connerr; } struct starter_config *confread_load(const char *file, err_t *perr, bool resolvip, char *ctlbase, bool setuponly) { struct starter_config *cfg = NULL; struct config_parsed *cfgp; struct section_list *sconn; bool err = FALSE; bool connerr; #ifdef DNSSEC struct ub_ctx *dnsctx = ub_ctx_create(); unbound_init(dnsctx); #else struct ub_ctx *dnsctx = NULL; #endif /** * Load file */ cfgp = parser_load_conf(file, perr); if (!cfgp) return NULL; cfg = (struct starter_config *)alloc_bytes(sizeof(struct starter_config),"starter_config cfg"); zero(cfg); /** * Set default values */ ipsecconf_default_values(cfg); if (ctlbase) { pfree(cfg->ctlbase); cfg->ctlbase = clone_str(ctlbase, "control socket"); } /** * Load setup */ err |= load_setup(cfg, cfgp); if (err) { parser_free_conf(cfgp); confread_free(cfg); return NULL; } if (!setuponly) { /** * Find %default and %oedefault conn * */ for (sconn = cfgp->sections.tqh_first; (!err) && sconn != NULL; sconn = sconn->link.tqe_next) { if (streq(sconn->name, "%default")) { starter_log(LOG_LEVEL_DEBUG, "Loading default conn"); err |= load_conn(dnsctx, &cfg->conn_default, cfgp, sconn, FALSE, /*default conn*/ TRUE, resolvip, perr); } if (streq(sconn->name, "%oedefault")) { starter_log(LOG_LEVEL_DEBUG, "Loading oedefault conn"); err |= load_conn(dnsctx, &cfg->conn_oedefault, cfgp, sconn, FALSE, /*default conn*/ TRUE, resolvip, perr); if (!err) cfg->got_oedefault = TRUE; } } /** * Load other conns */ for (sconn = cfgp->sections.tqh_first; sconn != NULL; sconn = sconn->link.tqe_next) { if (streq(sconn->name, "%default")) continue; if (streq(sconn->name, "%oedefault")) continue; connerr = init_load_conn(dnsctx, cfg, cfgp, sconn, FALSE, resolvip, perr); #if 0 /* ??? the following condition can never be true */ if (connerr == -1) { parser_free_conf(cfgp); confread_free(cfg); return NULL; } #endif err |= connerr; } /* if we have OE on, then create any missing OE conns! */ if (cfg->setup.options[KBF_OPPOENCRYPT]) { starter_log(LOG_LEVEL_DEBUG, "Enabling OE conns\n"); add_any_oeconns(cfg, cfgp); } } parser_free_conf(cfgp); return cfg; } static void confread_free_conn(struct starter_conn *conn) { int i; pfreeany(conn->left.iface); pfreeany(conn->left.id); pfreeany(conn->left.rsakey1); pfreeany(conn->left.rsakey2); pfreeany(conn->right.iface); pfreeany(conn->right.id); pfreeany(conn->right.rsakey1); pfreeany(conn->right.rsakey2); for (i = 0; i < KSCF_MAX; i++) { pfreeany(conn->left.strings[i]); pfreeany(conn->right.strings[i]); } for (i = 0; i < KSF_MAX; i++) pfreeany(conn->strings[i]); pfreeany(conn->connalias); pfreeany(conn->name); pfreeany(conn->esp); pfreeany(conn->ike); pfreeany(conn->modecfg_dns1); pfreeany(conn->modecfg_dns2); pfreeany(conn->left.virt); pfreeany(conn->right.virt); } void confread_free(struct starter_config *cfg) { int i; struct starter_conn *conn, *c; FREE_LIST(cfg->setup.interfaces); pfree(cfg->ctlbase); for (i = 0; i < KSF_MAX; i++) pfreeany(cfg->setup.strings[i]); confread_free_conn(&(cfg->conn_default)); for (conn = cfg->conns.tqh_first; conn != NULL; ) { c = conn; conn = conn->link.tqe_next; confread_free_conn(c); pfree(c); } pfree(cfg); }