| /* |
| * Copyright (C) 2014 Tobias Brunner |
| * |
| * Copyright (C) 2001-2002 Mathieu Lafon - Arkoon Network Security |
| * |
| * 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 <http://www.fsf.org/copyleft/gpl.txt>. |
| * |
| * 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 <sys/types.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <netdb.h> |
| |
| #include <library.h> |
| #include <utils/debug.h> |
| |
| #include "keywords.h" |
| #include "confread.h" |
| #include "args.h" |
| #include "files.h" |
| #include "parser/conf_parser.h" |
| |
| #define IKE_LIFETIME_DEFAULT 10800 /* 3 hours */ |
| #define IPSEC_LIFETIME_DEFAULT 3600 /* 1 hour */ |
| #define SA_REPLACEMENT_MARGIN_DEFAULT 540 /* 9 minutes */ |
| #define SA_REPLACEMENT_FUZZ_DEFAULT 100 /* 100% of margin */ |
| #define SA_REPLACEMENT_RETRIES_DEFAULT 3 |
| #define SA_REPLAY_WINDOW_DEFAULT -1 /* use charon.replay_window */ |
| |
| static const char firewall_defaults[] = IPSEC_SCRIPT " _updown iptables"; |
| |
| /** |
| * Process deprecated keywords |
| */ |
| static bool is_deprecated(kw_token_t token, char *name, char *conn) |
| { |
| switch (token) |
| { |
| case KW_SETUP_DEPRECATED: |
| case KW_PKCS11_DEPRECATED: |
| DBG1(DBG_APP, "# deprecated keyword '%s' in config setup", name); |
| break; |
| case KW_CONN_DEPRECATED: |
| case KW_END_DEPRECATED: |
| case KW_PFS_DEPRECATED: |
| DBG1(DBG_APP, "# deprecated keyword '%s' in conn '%s'", name, conn); |
| break; |
| case KW_CA_DEPRECATED: |
| DBG1(DBG_APP, "# deprecated keyword '%s' in ca '%s'", name, conn); |
| break; |
| default: |
| return FALSE; |
| } |
| /* additional messages for some */ |
| switch (token) |
| { |
| case KW_PKCS11_DEPRECATED: |
| DBG1(DBG_APP, " use the 'pkcs11' plugin instead"); |
| break; |
| case KW_PFS_DEPRECATED: |
| DBG1(DBG_APP, " PFS is enabled by specifying a DH group in the " |
| "'esp' cipher suite"); |
| break; |
| default: |
| break; |
| } |
| return TRUE; |
| } |
| |
| /* |
| * parse config setup section |
| */ |
| static void load_setup(starter_config_t *cfg, conf_parser_t *parser) |
| { |
| enumerator_t *enumerator; |
| dictionary_t *dict; |
| const kw_entry_t *entry; |
| char *key, *value; |
| |
| DBG2(DBG_APP, "Loading config setup"); |
| dict = parser->get_section(parser, CONF_PARSER_CONFIG_SETUP, NULL); |
| if (!dict) |
| { |
| return; |
| } |
| enumerator = dict->create_enumerator(dict); |
| while (enumerator->enumerate(enumerator, &key, &value)) |
| { |
| bool assigned = FALSE; |
| |
| entry = in_word_set(key, strlen(key)); |
| if (!entry) |
| { |
| DBG1(DBG_APP, "# unknown keyword '%s'", key); |
| cfg->non_fatal_err++; |
| continue; |
| } |
| if ((int)entry->token < KW_SETUP_FIRST || entry->token > KW_SETUP_LAST) |
| { |
| DBG1(DBG_APP, "# unsupported keyword '%s' in config setup", key); |
| cfg->err++; |
| continue; |
| } |
| if (is_deprecated(entry->token, key, "")) |
| { |
| cfg->non_fatal_err++; |
| continue; |
| } |
| if (!assign_arg(entry->token, KW_SETUP_FIRST, key, value, cfg, |
| &assigned)) |
| { |
| DBG1(DBG_APP, " bad argument value in config setup"); |
| cfg->err++; |
| } |
| } |
| enumerator->destroy(enumerator); |
| dict->destroy(dict); |
| } |
| |
| /* |
| * parse a ca section |
| */ |
| static void load_ca(starter_ca_t *ca, starter_config_t *cfg, |
| conf_parser_t *parser) |
| { |
| enumerator_t *enumerator; |
| dictionary_t *dict; |
| const kw_entry_t *entry; |
| kw_token_t token; |
| char *key, *value; |
| |
| DBG2(DBG_APP, "Loading ca '%s'", ca->name); |
| dict = parser->get_section(parser, CONF_PARSER_CA, ca->name); |
| if (!dict) |
| { |
| return; |
| } |
| enumerator = dict->create_enumerator(dict); |
| while (enumerator->enumerate(enumerator, &key, &value)) |
| { |
| bool assigned = FALSE; |
| |
| entry = in_word_set(key, strlen(key)); |
| if (!entry) |
| { |
| DBG1(DBG_APP, "# unknown keyword '%s'", key); |
| cfg->non_fatal_err++; |
| continue; |
| } |
| token = entry->token; |
| if (token == KW_AUTO) |
| { |
| token = KW_CA_SETUP; |
| } |
| if (token < KW_CA_FIRST || token > KW_CA_LAST) |
| { |
| DBG1(DBG_APP, "# unsupported keyword '%s' in ca '%s'", |
| key, ca->name); |
| cfg->err++; |
| continue; |
| } |
| if (is_deprecated(token, key, ca->name)) |
| { |
| cfg->non_fatal_err++; |
| continue; |
| } |
| if (!assign_arg(token, KW_CA_FIRST, key, value, ca, &assigned)) |
| { |
| DBG1(DBG_APP, " bad argument value in ca '%s'", ca->name); |
| cfg->err++; |
| } |
| } |
| enumerator->destroy(enumerator); |
| dict->destroy(dict); |
| |
| /* treat 'route' and 'start' as 'add' */ |
| if (ca->startup != STARTUP_NO) |
| { |
| ca->startup = STARTUP_ADD; |
| } |
| } |
| |
| /* |
| * set some default values |
| */ |
| static void conn_defaults(starter_conn_t *conn) |
| { |
| conn->startup = STARTUP_NO; |
| conn->state = STATE_IGNORE; |
| conn->mode = MODE_TUNNEL; |
| conn->options = SA_OPTION_MOBIKE; |
| |
| /* esp defaults are set after parsing the conn section */ |
| conn->sa_ike_life_seconds = IKE_LIFETIME_DEFAULT; |
| conn->sa_ipsec_life_seconds = IPSEC_LIFETIME_DEFAULT; |
| conn->sa_rekey_margin = SA_REPLACEMENT_MARGIN_DEFAULT; |
| conn->sa_rekey_fuzz = SA_REPLACEMENT_FUZZ_DEFAULT; |
| conn->sa_keying_tries = SA_REPLACEMENT_RETRIES_DEFAULT; |
| conn->install_policy = TRUE; |
| conn->dpd_delay = 30; /* seconds */ |
| conn->dpd_timeout = 150; /* seconds */ |
| conn->replay_window = SA_REPLAY_WINDOW_DEFAULT; |
| conn->fragmentation = FRAGMENTATION_YES; |
| |
| conn->left.sendcert = CERT_SEND_IF_ASKED; |
| conn->right.sendcert = CERT_SEND_IF_ASKED; |
| |
| conn->left.ikeport = 500; |
| conn->right.ikeport = 500; |
| |
| conn->left.to_port = 0xffff; |
| conn->right.to_port = 0xffff; |
| } |
| |
| /* |
| * parse left|right specific options |
| */ |
| static void kw_end(starter_conn_t *conn, starter_end_t *end, kw_token_t token, |
| char *key, char *value, starter_config_t *cfg) |
| { |
| bool assigned = FALSE; |
| |
| if (is_deprecated(token, key, conn->name)) |
| { |
| cfg->non_fatal_err++; |
| return; |
| } |
| |
| if (!assign_arg(token, KW_END_FIRST, key, value, end, &assigned)) |
| { |
| goto err; |
| } |
| |
| /* post processing of some keywords that were assigned automatically */ |
| switch (token) |
| { |
| case KW_HOST: |
| if (value && strlen(value) > 0 && value[0] == '%') |
| { |
| if (streq(value, "%defaultroute")) |
| { |
| value = "%any"; |
| } |
| if (!streq(value, "%any") && !streq(value, "%any4") && |
| !streq(value, "%any6")) |
| { /* allow_any prefix */ |
| end->allow_any = TRUE; |
| value++; |
| } |
| } |
| free(end->host); |
| end->host = strdupnull(value); |
| break; |
| case KW_SOURCEIP: |
| conn->mode = MODE_TUNNEL; |
| conn->proxy_mode = FALSE; |
| break; |
| case KW_SENDCERT: |
| if (end->sendcert == CERT_YES_SEND) |
| { |
| end->sendcert = CERT_ALWAYS_SEND; |
| } |
| else if (end->sendcert == CERT_NO_SEND) |
| { |
| end->sendcert = CERT_NEVER_SEND; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| if (assigned) |
| { |
| return; |
| } |
| |
| /* individual processing of keywords that were not assigned automatically */ |
| switch (token) |
| { |
| case KW_PROTOPORT: |
| { |
| struct protoent *proto; |
| struct servent *svc; |
| char *sep, *port = "", *endptr; |
| long int p; |
| |
| sep = strchr(value, '/'); |
| if (sep) |
| { /* protocol/port */ |
| *sep = '\0'; |
| port = sep + 1; |
| } |
| |
| if (streq(value, "%any")) |
| { |
| end->protocol = 0; |
| } |
| else |
| { |
| proto = getprotobyname(value); |
| if (proto) |
| { |
| end->protocol = proto->p_proto; |
| } |
| else |
| { |
| p = strtol(value, &endptr, 0); |
| if ((*value && *endptr) || p < 0 || p > 0xff) |
| { |
| DBG1(DBG_APP, "# bad protocol: %s=%s", key, value); |
| goto err; |
| } |
| end->protocol = (uint8_t)p; |
| } |
| } |
| if (streq(port, "%any")) |
| { |
| end->from_port = 0; |
| end->to_port = 0xffff; |
| } |
| else if (streq(port, "%opaque")) |
| { |
| end->from_port = 0xffff; |
| end->to_port = 0; |
| } |
| else if (*port) |
| { |
| svc = getservbyname(port, NULL); |
| if (svc) |
| { |
| end->from_port = end->to_port = ntohs(svc->s_port); |
| } |
| else |
| { |
| p = strtol(port, &endptr, 0); |
| if (p < 0 || p > 0xffff) |
| { |
| DBG1(DBG_APP, "# bad port: %s=%s", key, port); |
| goto err; |
| } |
| end->from_port = p; |
| if (*endptr == '-') |
| { |
| port = endptr + 1; |
| p = strtol(port, &endptr, 0); |
| if (p < 0 || p > 0xffff) |
| { |
| DBG1(DBG_APP, "# bad port: %s=%s", key, port); |
| goto err; |
| } |
| } |
| end->to_port = p; |
| if (*endptr) |
| { |
| DBG1(DBG_APP, "# bad port: %s=%s", key, port); |
| goto err; |
| } |
| } |
| } |
| if (sep) |
| { /* restore the original text in case also= is used */ |
| *sep = '/'; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| return; |
| |
| err: |
| DBG1(DBG_APP, " bad argument value in conn '%s'", conn->name); |
| cfg->err++; |
| } |
| |
| /* |
| * macro to handle simple flags |
| */ |
| #define KW_SA_OPTION_FLAG(sy, sn, fl) \ |
| if (streq(value, sy)) { conn->options |= fl; } \ |
| else if (streq(value, sn)) { conn->options &= ~fl; } \ |
| else { DBG1(DBG_APP, "# bad option value: %s=%s", key, value); cfg->err++; } |
| |
| /* |
| * parse settings not handled by the simple argument parser |
| */ |
| static void handle_keyword(kw_token_t token, starter_conn_t *conn, char *key, |
| char *value, starter_config_t *cfg) |
| { |
| if ((token == KW_ESP && conn->ah) || (token == KW_AH && conn->esp)) |
| { |
| DBG1(DBG_APP, "# can't have both 'ah' and 'esp' options"); |
| cfg->err++; |
| return; |
| } |
| switch (token) |
| { |
| case KW_TYPE: |
| { |
| conn->mode = MODE_TRANSPORT; |
| conn->proxy_mode = FALSE; |
| if (streq(value, "tunnel")) |
| { |
| conn->mode = MODE_TUNNEL; |
| } |
| else if (streq(value, "beet")) |
| { |
| conn->mode = MODE_BEET; |
| } |
| else if (streq(value, "transport_proxy")) |
| { |
| conn->mode = MODE_TRANSPORT; |
| conn->proxy_mode = TRUE; |
| } |
| else if (streq(value, "passthrough") || streq(value, "pass")) |
| { |
| conn->mode = MODE_PASS; |
| } |
| else if (streq(value, "drop") || streq(value, "reject")) |
| { |
| conn->mode = MODE_DROP; |
| } |
| else if (!streq(value, "transport")) |
| { |
| DBG1(DBG_APP, "# bad policy value: %s=%s", key, value); |
| cfg->err++; |
| } |
| break; |
| } |
| case KW_COMPRESS: |
| KW_SA_OPTION_FLAG("yes", "no", SA_OPTION_COMPRESS) |
| break; |
| case KW_MARK: |
| if (!mark_from_string(value, MARK_OP_UNIQUE, &conn->mark_in)) |
| { |
| cfg->err++; |
| break; |
| } |
| conn->mark_out = conn->mark_in; |
| break; |
| case KW_MARK_IN: |
| if (!mark_from_string(value, MARK_OP_UNIQUE, &conn->mark_in)) |
| { |
| cfg->err++; |
| } |
| break; |
| case KW_MARK_OUT: |
| if (!mark_from_string(value, MARK_OP_UNIQUE, &conn->mark_out)) |
| { |
| cfg->err++; |
| } |
| break; |
| case KW_TFC: |
| if (streq(value, "%mtu")) |
| { |
| conn->tfc = -1; |
| } |
| else |
| { |
| char *endptr; |
| |
| conn->tfc = strtoul(value, &endptr, 10); |
| if (*endptr != '\0') |
| { |
| DBG1(DBG_APP, "# bad integer value: %s=%s", key, value); |
| cfg->err++; |
| } |
| } |
| break; |
| case KW_KEYINGTRIES: |
| if (streq(value, "%forever")) |
| { |
| conn->sa_keying_tries = 0; |
| } |
| else |
| { |
| char *endptr; |
| |
| conn->sa_keying_tries = strtoul(value, &endptr, 10); |
| if (*endptr != '\0') |
| { |
| DBG1(DBG_APP, "# bad integer value: %s=%s", key, value); |
| cfg->err++; |
| } |
| } |
| break; |
| case KW_REKEY: |
| KW_SA_OPTION_FLAG("no", "yes", SA_OPTION_DONT_REKEY) |
| break; |
| case KW_REAUTH: |
| KW_SA_OPTION_FLAG("no", "yes", SA_OPTION_DONT_REAUTH) |
| break; |
| case KW_MOBIKE: |
| KW_SA_OPTION_FLAG("yes", "no", SA_OPTION_MOBIKE) |
| break; |
| case KW_FORCEENCAPS: |
| KW_SA_OPTION_FLAG("yes", "no", SA_OPTION_FORCE_ENCAP) |
| break; |
| case KW_MODECONFIG: |
| KW_SA_OPTION_FLAG("push", "pull", SA_OPTION_MODECFG_PUSH) |
| break; |
| case KW_XAUTH: |
| KW_SA_OPTION_FLAG("server", "client", SA_OPTION_XAUTH_SERVER) |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * handles left|rightfirewall and left|rightupdown parameters |
| */ |
| static void handle_firewall(const char *label, starter_end_t *end, |
| starter_config_t *cfg) |
| { |
| if (end->firewall) |
| { |
| if (end->updown != NULL) |
| { |
| DBG1(DBG_APP, "# cannot have both %sfirewall and %supdown", label, |
| label); |
| cfg->err++; |
| } |
| else |
| { |
| end->updown = strdupnull(firewall_defaults); |
| end->firewall = FALSE; |
| } |
| } |
| } |
| |
| /* |
| * parse a conn section |
| */ |
| static void load_conn(starter_conn_t *conn, starter_config_t *cfg, |
| conf_parser_t *parser) |
| { |
| enumerator_t *enumerator; |
| dictionary_t *dict; |
| const kw_entry_t *entry; |
| kw_token_t token; |
| char *key, *value; |
| |
| DBG2(DBG_APP, "Loading conn '%s'", conn->name); |
| dict = parser->get_section(parser, CONF_PARSER_CONN, conn->name); |
| if (!dict) |
| { |
| return; |
| } |
| enumerator = dict->create_enumerator(dict); |
| while (enumerator->enumerate(enumerator, &key, &value)) |
| { |
| bool assigned = FALSE; |
| |
| entry = in_word_set(key, strlen(key)); |
| if (!entry) |
| { |
| DBG1(DBG_APP, "# unknown keyword '%s'", key); |
| cfg->non_fatal_err++; |
| continue; |
| } |
| token = entry->token; |
| if (token >= KW_LEFT_FIRST && token <= KW_LEFT_LAST) |
| { |
| kw_end(conn, &conn->left, token - KW_LEFT_FIRST + KW_END_FIRST, |
| key, value, cfg); |
| continue; |
| } |
| else if (token >= KW_RIGHT_FIRST && token <= KW_RIGHT_LAST) |
| { |
| kw_end(conn, &conn->right, token - KW_RIGHT_FIRST + KW_END_FIRST, |
| key, value, cfg); |
| continue; |
| } |
| if (token == KW_AUTO) |
| { |
| token = KW_CONN_SETUP; |
| } |
| if (token < KW_CONN_FIRST || token > KW_CONN_LAST) |
| { |
| DBG1(DBG_APP, "# unsupported keyword '%s' in conn '%s'", |
| key, conn->name); |
| cfg->err++; |
| continue; |
| } |
| if (is_deprecated(token, key, conn->name)) |
| { |
| cfg->non_fatal_err++; |
| continue; |
| } |
| if (!assign_arg(token, KW_CONN_FIRST, key, value, conn, |
| &assigned)) |
| { |
| DBG1(DBG_APP, " bad argument value in conn '%s'", conn->name); |
| cfg->err++; |
| continue; |
| } |
| if (!assigned) |
| { |
| handle_keyword(token, conn, key, value, cfg); |
| } |
| } |
| enumerator->destroy(enumerator); |
| dict->destroy(dict); |
| |
| handle_firewall("left", &conn->left, cfg); |
| handle_firewall("right", &conn->right, cfg); |
| } |
| |
| /* |
| * free the memory used by a starter_ca_t object |
| */ |
| static void confread_free_ca(starter_ca_t *ca) |
| { |
| free_args(KW_CA_NAME, KW_CA_LAST, (char *)ca); |
| free(ca); |
| } |
| |
| /* |
| * free the memory used by a starter_conn_t object |
| */ |
| static void confread_free_conn(starter_conn_t *conn) |
| { |
| free_args(KW_END_FIRST, KW_END_LAST, (char *)&conn->left); |
| free_args(KW_END_FIRST, KW_END_LAST, (char *)&conn->right); |
| free_args(KW_CONN_NAME, KW_CONN_LAST, (char *)conn); |
| free(conn); |
| } |
| |
| /* |
| * free the memory used by a starter_config_t object |
| */ |
| void confread_free(starter_config_t *cfg) |
| { |
| starter_conn_t *conn = cfg->conn_first; |
| starter_ca_t *ca = cfg->ca_first; |
| |
| free_args(KW_SETUP_FIRST, KW_SETUP_LAST, (char *)cfg); |
| |
| while (conn != NULL) |
| { |
| starter_conn_t *conn_aux = conn; |
| |
| conn = conn->next; |
| confread_free_conn(conn_aux); |
| } |
| |
| while (ca != NULL) |
| { |
| starter_ca_t *ca_aux = ca; |
| |
| ca = ca->next; |
| confread_free_ca(ca_aux); |
| } |
| |
| free(cfg); |
| } |
| |
| /* |
| * load and parse an IPsec configuration file |
| */ |
| starter_config_t* confread_load(const char *file) |
| { |
| conf_parser_t *parser; |
| starter_config_t *cfg = NULL; |
| enumerator_t *enumerator; |
| u_int total_err; |
| char *name; |
| |
| parser = conf_parser_create(file); |
| if (!parser->parse(parser)) |
| { |
| parser->destroy(parser); |
| return NULL; |
| } |
| |
| INIT(cfg, |
| .setup = { |
| .uniqueids = TRUE, |
| } |
| ); |
| |
| /* load config setup section */ |
| load_setup(cfg, parser); |
| |
| /* load ca sections */ |
| enumerator = parser->get_sections(parser, CONF_PARSER_CA); |
| while (enumerator->enumerate(enumerator, &name)) |
| { |
| u_int previous_err = cfg->err; |
| starter_ca_t *ca; |
| |
| INIT(ca, |
| .name = strdup(name), |
| ); |
| load_ca(ca, cfg, parser); |
| |
| if (cfg->err > previous_err) |
| { |
| total_err = cfg->err - previous_err; |
| DBG1(DBG_APP, "# ignored ca '%s' due to %d parsing error%s", name, |
| total_err, (total_err > 1) ? "s" : ""); |
| confread_free_ca(ca); |
| cfg->non_fatal_err += cfg->err - previous_err; |
| cfg->err = previous_err; |
| } |
| else |
| { |
| if (cfg->ca_last) |
| { |
| cfg->ca_last->next = ca; |
| } |
| cfg->ca_last = ca; |
| if (!cfg->ca_first) |
| { |
| cfg->ca_first = ca; |
| } |
| if (ca->startup != STARTUP_NO) |
| { |
| ca->state = STATE_TO_ADD; |
| } |
| } |
| } |
| enumerator->destroy(enumerator); |
| |
| /* load conn sections */ |
| enumerator = parser->get_sections(parser, CONF_PARSER_CONN); |
| while (enumerator->enumerate(enumerator, &name)) |
| { |
| u_int previous_err = cfg->err; |
| starter_conn_t *conn; |
| |
| INIT(conn, |
| .name = strdup(name), |
| ); |
| conn_defaults(conn); |
| load_conn(conn, cfg, parser); |
| |
| if (cfg->err > previous_err) |
| { |
| total_err = cfg->err - previous_err; |
| DBG1(DBG_APP, "# ignored conn '%s' due to %d parsing error%s", name, |
| total_err, (total_err > 1) ? "s" : ""); |
| confread_free_conn(conn); |
| cfg->non_fatal_err += cfg->err - previous_err; |
| cfg->err = previous_err; |
| } |
| else |
| { |
| if (cfg->conn_last) |
| { |
| cfg->conn_last->next = conn; |
| } |
| cfg->conn_last = conn; |
| if (!cfg->conn_first) |
| { |
| cfg->conn_first = conn; |
| } |
| if (conn->startup != STARTUP_NO) |
| { |
| conn->state = STATE_TO_ADD; |
| } |
| } |
| } |
| enumerator->destroy(enumerator); |
| |
| parser->destroy(parser); |
| |
| total_err = cfg->err + cfg->non_fatal_err; |
| if (total_err > 0) |
| { |
| DBG1(DBG_APP, "### %d parsing error%s (%d fatal) ###", |
| total_err, (total_err > 1)?"s":"", cfg->err); |
| } |
| return cfg; |
| } |