diff options
Diffstat (limited to 'tacplus-daemon/parser.c')
-rw-r--r-- | tacplus-daemon/parser.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/tacplus-daemon/parser.c b/tacplus-daemon/parser.c new file mode 100644 index 0000000..5684769 --- /dev/null +++ b/tacplus-daemon/parser.c @@ -0,0 +1,317 @@ +/* + TACACS+ D-Bus Daemon code + + Copyright (c) 2018-2019 AT&T Intellectual Property. + Copyright (c) 2015-2016 Brocade Communications Systems, Inc. + + SPDX-License-Identifier: GPL-2.0-only +*/ + +#include <assert.h> +#include <glib.h> +#include <netinet/ip.h> +#include <stdbool.h> + +#include "parser.h" +#include "utils.h" + +/* TODO: set max config size + * header file + * make more versatile/flexible + */ + +static const char *s_general = "general"; +static const char *s_options = "options"; + +static inline +void g_syslog(int priority, const char *fmt, GError *e) +{ + syslog(priority, fmt, e->message); + g_error_free(e); +} + +static inline +void sa_set_port(struct sockaddr *sa, ushort port) +{ + if (!sa) { + syslog(LOG_CRIT, "sa should never be NULL"); + exit(1); + } + + if (sa->sa_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *) sa; + sin->sin_port = htons(port); + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; + sin6->sin6_port = htons(port); + } +} + +static +bool isReservedSection(const char *name) +{ + return (!strcmp(name, s_general) || !strcmp(name, s_options)); +} + +void read_config(const char *f_name, struct tacplus_options **opts) +{ + GKeyFile *keyfile; + GKeyFileFlags flags; + GError *error = NULL; + gsize length; + const struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_NUMERICHOST, + }; + struct connection conn[TACPLUS_MAX_SERVERS]; + unsigned nservers, setupTimeout; + int dscp; + unsigned i, j; + + nservers = 0; + + /* initialize key file */ + keyfile = g_key_file_new(); + flags = G_KEY_FILE_NONE; + + if (!g_key_file_load_from_file(keyfile, f_name, flags, &error)) { + g_syslog(LOG_ERR, "config file open: %s", error); + g_key_file_free(keyfile); + return; + } + + /* get the names of sections; any section that doesn't have + * a reserved name is assumed to be a server configuration. + */ + + char **sections = g_key_file_get_groups(keyfile, &length); + + /* any global options would be handled here from the + * 'general' section + */ + + gboolean broadcast = g_key_file_get_boolean(keyfile, s_general, "BroadcastAccounting", &error); + if (error) { + g_syslog(LOG_ERR, "parse BroadcastAccounting option: %s", error); + goto cleanup2; + } + +#ifndef HAVE_LIBTAC_EVENT + if (broadcast) { + syslog(LOG_ERR, "Cannot enable unsupported BroadcastAccounting option"); + broadcast = false; + } +#endif + + setupTimeout = g_key_file_get_integer(keyfile, s_general, "SetupTimeout", &error); + if (error) { + g_syslog(LOG_ERR, "parse SetupTimeout option: %s", error); + goto cleanup2; + } + if (setupTimeout <= 0) { + syslog(LOG_ERR, "parse SetupTimeout option must be higher than 0.\n"); + goto cleanup2; + } + + dscp = g_key_file_get_integer(keyfile, s_general, "Dscp", &error); + if (error) { + if (error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) { + g_syslog(LOG_ERR, "parse Dscp option: %s", error); + goto cleanup2; + } + dscp = IPTOS_CLASS_CS6; + g_error_free(error); + } else if (dscp < 0 || dscp > 63) { + syslog(LOG_ERR, "Dscp value must be in the range <0-63>"); + goto cleanup2; + } else { + // The two least significant bits are used for ECN + dscp <<= 2; + } + + for (i = j = 0; i < length; ++i) { + int hold_down, port, timeout, err; + gchar *gsecret, *addr, *src_addr, *gsrc_intf; + struct addrinfo *ai, *pai, *sai; + gchar *server = sections[i]; + const char *secret, *src_intf; + bool valid = true; + + if (isReservedSection(server)) + continue; + + /* Clear variables which may be set from previous iterations */ + error = NULL; + ai = pai = sai = NULL; + + /* start with tuple of address and port */ + addr = g_key_file_get_string(keyfile, server, "Address", &error); + if (error) { + g_syslog(LOG_ERR, "parse server address: %s", error); + ai = NULL; + valid = false; + } else { + /* ignore the port, we'll patch it in later... */ + err = getaddrinfo(addr, NULL, &hints, &ai); + if (err != 0) { + syslog(LOG_ERR, "parse server address: %s", + gai_strerror(err)); + valid = false; + } + } + + port = g_key_file_get_integer(keyfile, server, "Port", &error); + if (error) { + g_syslog(LOG_ERR, "parse port: %s", error); + valid = false; + } + + /* ... then required secret and timeout. */ + gsecret = g_key_file_get_string(keyfile, server, "Secret", &error); + if (error) { + g_syslog(LOG_ERR, "parse secret: %s", error); + valid = false; + secret = NULL; + } else { + /* we do this so that the special memory destruction + * that glib requires is confined to this module. + */ + secret = strdup(gsecret); + if (! secret) { + syslog(LOG_CRIT, "tacplus secret allocation fail: out-of-memory"); + valid = false; + } + g_free(gsecret); + } + + timeout = g_key_file_get_integer(keyfile, server, "Timeout", &error); + if (error) { + g_syslog(LOG_ERR, "parse timeout: %s", error); + valid = false; + } + + hold_down = g_key_file_get_integer(keyfile, server, "HoldDown", &error); + if (error) { + g_syslog(LOG_ERR, "parse HoldDown: %s", error); + valid = false; + } else if (hold_down < 0) { + syslog(LOG_ERR, "Invalid negative HoldDown %d", hold_down); + valid = false; + } + + /* substitute in port # */ + for (pai = ai; pai != NULL; pai = pai->ai_next) + /* if pai is non-NULL, then pai->ai_addr should + * also never be NULL... + */ + sa_set_port(pai->ai_addr, port); + + /* SourceAddress, which is optional... */ + src_addr = g_key_file_get_string(keyfile, server, "SourceAddress", &error); + if (error) { + if (error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) + g_syslog(LOG_ERR, "parse source address: %s", + error); + else + g_error_free(error); + + sai = NULL; + } else { + err = getaddrinfo(src_addr, "", &hints, &sai); + if (err != 0) { + syslog(LOG_ERR, "parse source address: %s", + gai_strerror(err)); + valid = false; + } + } + + /* Optional SourceInterface */ + error = NULL; + gsrc_intf = g_key_file_get_string(keyfile, server, "SourceInterface", &error); + if (error) { + if (error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) { + g_syslog(LOG_ERR, "parse source interface: %s", error); + valid = false; + } + else + g_error_free(error); + src_intf = NULL; + } else { + src_intf = strdup (gsrc_intf); + if (! src_intf) { + syslog(LOG_CRIT, "tacplus source interface allocation " + "fail: out-of-memory"); + valid = false; + } + g_free(gsrc_intf); + } + + if (!valid) + goto cleanup; + + if (j == TACPLUS_MAX_SERVERS) { + syslog(LOG_WARNING, + "too many servers configured: ignoring %s", server); + goto cleanup; + } + + conn[j].addr = ai; + conn[j].secret = secret; + conn[j].timeout = timeout; + conn[j].hold_down = hold_down; + conn[j].src_addr = sai; + conn[j].src_intf = src_intf; + ++j; + +cleanup: + g_free((char *)addr); + g_free((char *)src_addr); + + if (valid) + continue; + + if (ai) + freeaddrinfo(ai); + if (sai) + freeaddrinfo(sai); + } + + nservers = j; + +cleanup2: + /* release stuff... */ + g_strfreev(sections); + g_key_file_free(keyfile); + + if (!nservers) { + syslog(LOG_ERR, "no servers configured"); + exit(1); + } + + *opts = tacplus_options_alloc(nservers); + if (!opts) { + syslog(LOG_CRIT, "tacplus_options allocation fail: out-of-memory"); + exit(1); + } + + (*opts)->next_server = INVALID_SERVER_ID; + (*opts)->curr_server = 0; + (*opts)->broadcast = broadcast; + (*opts)->dscp = dscp; + (*opts)->setupTimeout = setupTimeout; + + for (i = 0; i < nservers; ++i) { + (*opts)->server[i].id = i; + (*opts)->server[i].addrs = conn[i].addr; + (*opts)->server[i].src_addrs = conn[i].src_addr; + (*opts)->server[i].src_intf = conn[i].src_intf; + (*opts)->server[i].secret = conn[i].secret; + (*opts)->server[i].timeout = conn[i].timeout; + (*opts)->server[i].hold_down = conn[i].hold_down; +#ifdef HAVE_LIBTAC_EVENT + (*opts)->server[i].session = NULL; +#endif + } +} + |