From a78037e5768319a9075e06ccdbe6b53397e532b2 Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Sun, 7 Apr 2019 17:48:05 +0200 Subject: [PATCH 01/15] Fix warnings/errors pointed out by clang --- src/dhcpoptinj.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/dhcpoptinj.c b/src/dhcpoptinj.c index 48cd5c3..ffafdc1 100644 --- a/src/dhcpoptinj.c +++ b/src/dhcpoptinj.c @@ -42,12 +42,14 @@ #define MIN_BOOTP_SIZE 300 +#pragma pack(1) struct Packet { struct IPv4Header ipHeader; struct UDPHeader udpHeader; struct BootP bootp; -} __attribute__((packed)); +}; +#pragma pack() enum MangleResult { @@ -57,7 +59,8 @@ enum MangleResult }; /* Somewhat arbitrary, feel free to change */ -static const uint32_t maxPacketSize = 2048; +#define MAX_PACKET_SIZE 2048 + /* The netfilter queue length 20 is also arbitrary. Hopefully it is * sufficient. */ static const uint32_t maxQueueLen = 20; @@ -135,7 +138,7 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); } - if (nfq_set_mode(queue, NFQNL_COPY_PACKET, maxPacketSize) < 0) + if (nfq_set_mode(queue, NFQNL_COPY_PACKET, MAX_PACKET_SIZE) < 0) { logMessage(LOG_ERR, "Failed to set netfilter queue mode: %s\n", strerror( errno)); @@ -175,7 +178,7 @@ int main(int argc, char *argv[]) int queueFd = nfq_fd(nfq); for (; !escapeMainLoop; ) { - char packet[maxPacketSize] __attribute__((aligned)); + char packet[MAX_PACKET_SIZE] __attribute__((aligned)); ssize_t bytes = recv(queueFd, packet, sizeof(packet), 0); if (bytes < -1) { @@ -479,6 +482,7 @@ static enum MangleResult mangleOptions(const uint8_t *origData, size_t origDataS return Mangle_OK; } +__attribute__((__format__ (__printf__, 2, 0))) static void logMessage(int priority, const char *format, ...) { if (priority == LOG_DEBUG && !config->debug) @@ -638,7 +642,7 @@ static void debugLogOptionFound(const struct DHCPOption *option) logMessage(LOG_DEBUG,"Found END option %s\n", config->dhcpOptCodeCount ? "(removing)" : "(copying)"); else if (option->code == DHCPOPT_TYPE && option->length == 1) - logMessage(LOG_DEBUG, "Found option %' '3hhu (0x%02hhX) (DHCP message type) %s", + logMessage(LOG_DEBUG, "Found option % 3hhd (0x%02hhX) (DHCP message type) %s", option->code, option->code, dhcp_msgTypeString(option->data[0])); else debugLogOption("Found", option); @@ -660,7 +664,7 @@ static void debugLogOption(const char *action, const struct DHCPOption *option) const char *optName = dhcp_optionString(option->code); size_t optNameLen = strlen(optName); const size_t alignedWidth = 24; - logMessage(LOG_DEBUG, "%s option %' '3hhu (0x%02hhX) (%s)%*s with %' '3u-byte payload %s", + logMessage(LOG_DEBUG, "%s option % 3hhd (0x%02hhX) (%s)%*s with % 3d-byte payload %s", action, option->code, option->code, From 3575bf898b7536a58428f526977ce95acd411cc2 Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Sat, 6 Apr 2019 20:13:39 +0200 Subject: [PATCH 02/15] Add support for parsing configuration file --- CMakeLists.txt | 1 + src/config.c | 585 +++++++++++++++++++++++++++++++++-------------- src/config.h | 2 +- src/dhcpoptinj.c | 4 +- src/options.c | 5 + src/options.h | 1 + 6 files changed, 420 insertions(+), 178 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a40c4a..b0b40d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ target_compile_options(dhcpoptinj PRIVATE -Wold-style-definition -fstack-protector -Wwrite-strings + -Wmissing-field-initializers -D_POSIX_SOURCE -D_DEFAULT_SOURCE -D_FORTIFY_SOURCE=2 diff --git a/src/config.c b/src/config.c index 23af516..fcc7d30 100644 --- a/src/config.c +++ b/src/config.c @@ -26,202 +26,173 @@ #include "options.h" #include #include "dhcp.h" +#include +#include +static const char programName[] = "dhcpoptinj"; static const char defaultPIDFilePath[] = "/var/run/dhcpoptinj.pid"; +static const char defaultConfFilePath[] = "/etc/dhcpoptinj.conf"; + +/* DHCP option lists for later serialisation: One for command line input and + * one for configuration file(s). They need to be separated so that one source + * does not override the other; the configuration file may be read in the + * middle of the command line option parsing. */ +static struct DHCPOptList *cmdDHCPOptList, *fileDHCPOptList; + +enum Source +{ + SOURCE_CMD_LINE = 0, + SOURCE_FILE, +}; + +enum ConfFileParseOption +{ + PARSE_ALLOW_NOEXIST = 0, + PARSE_REQUIRE_EXIST = 1, +}; + +/* Definitions for long-only options that cannot be identified with an ASCII + * character: */ +enum LongOnlyOpt { + ForwardOnFail = 1000, +}; + +/* Option definitions used to index options[] and optionCount[]: */ +enum Option +{ + OPT_CONF_FILE = 0, + OPT_DEBUG, + OPT_FOREGROUND, + OPT_FORWARD_ON_FAIL, + OPT_HELP, + OPT_IGNORE_EXISTING_OPT, + OPT_OPTION, + OPT_PID_FILE, + OPT_QUEUE, + OPT_REMOVE_EXISTING_OPT, + OPT_VERSION, + + OPT_COUNT, +}; + +static const int sources[] = +{ + SOURCE_CMD_LINE, + SOURCE_FILE, +}; + +static const struct option options[] = +{ + [OPT_CONF_FILE] = { "conf-file", optional_argument, NULL, 'c' }, + [OPT_DEBUG] = { "debug", no_argument, NULL, 'd' }, + [OPT_FOREGROUND] = { "foreground", no_argument, NULL, 'f' }, + [OPT_FORWARD_ON_FAIL] = { "forward-on-fail", no_argument, NULL, ForwardOnFail }, + [OPT_HELP] = { "help", no_argument, NULL, 'h' }, + [OPT_IGNORE_EXISTING_OPT] = { "ignore-existing-opt", no_argument, NULL, 'i' }, + [OPT_OPTION] = { "option", required_argument, NULL, 'o' }, + [OPT_PID_FILE] = { "pid-file", optional_argument, NULL, 'p' }, + [OPT_QUEUE] = { "queue", required_argument, NULL, 'q' }, + [OPT_REMOVE_EXISTING_OPT] = { "remove-existing-opt", no_argument, NULL, 'r' }, + [OPT_VERSION] = { "version", no_argument, NULL, 'v' }, + [OPT_COUNT] = {0}, +}; +/* Count the number of times arguments have been passed on the command line + * and listed as keywords in the configuration file: */ +static unsigned int optionCount[][OPT_COUNT] = +{ + [SOURCE_CMD_LINE] = {0}, + [SOURCE_FILE] = {0}, +}; static struct Config *createDefaultConfig(void); -static void printUsage(const char *programName); -static void printHelp(const char *programName); -static void printVersion(const char *programName); +static void printUsage(void); +static void printHelp(void); +static void printVersion(void); static int parseQueueNum(const char *string, uint16_t *queueNum); static void addDHCPOption(struct DHCPOptList *list, const char *string); +static void parseConfFile(struct Config *config, const char *filePath, int parseOpts); +static void parseOption(struct Config *config, int option, char *arg, enum Source source); +static void validateOptionCombinations(void); +static unsigned int totalOptionCount(int option); +static char *trim(char *text); +static int parseKeyValue(const char *key, const char *value, const char *filePath, + unsigned lineNo); + -struct Config *conf_parseOpts(int argc, char **argv) +struct Config *conf_parseOpts(int argc, char * const *argv) { - struct DHCPOptList *dhcpOptList = dhcpOpt_createList(); - if (!dhcpOptList) + cmdDHCPOptList = dhcpOpt_createList(); + fileDHCPOptList = dhcpOpt_createList(); + if (!cmdDHCPOptList || !fileDHCPOptList) { fputs("Failed to allocate memory for DHCP option list\n", stderr); exit(EXIT_FAILURE); } - enum LongOnlyOpts { - ForwardOnFail = 1000, - }; - struct Config *config = createDefaultConfig(); - const struct option options[] = - { - { "debug", no_argument, NULL, 'd' }, - { "foreground", no_argument, NULL, 'f' }, - { "forward-on-fail", no_argument, NULL, ForwardOnFail }, - { "help", no_argument, NULL, 'h' }, - { "ignore-existing-opt", no_argument, NULL, 'i' }, - { "option", required_argument, NULL, 'o' }, - { "pid-file", optional_argument, NULL, 'p' }, - { "queue", required_argument, NULL, 'q' }, - { "remove-existing-opt", no_argument, NULL, 'r' }, - { "version", no_argument, NULL, 'v' }, - { NULL, 0, NULL, 0 }, - }; - - int dFlagCount = 0; - int fFlagCount = 0; - int fwdOnFailFlagCount = 0; - int iFlagCount = 0; - int oFlagCount = 0; - int pFlagCount = 0; - int qFlagCount = 0; - int rFlagCount = 0; - - for (;;) + + while (true) { - int opt = getopt_long(argc, argv, "dfhio:p::q:rv", options, NULL); + int optVal = getopt_long(argc, argv, "c::dfhio:p::q:rv", options, NULL); /* Parsing finished: */ - if (opt == -1) + if (optVal == -1) break; - switch (opt) + int option = 0; + for (; option < OPT_COUNT; ++option) { - case 'd': - ++dFlagCount; - config->debug = true; - break; - - case 'f': - ++fFlagCount; - config->foreground = true; - break; - - case ForwardOnFail: - ++fwdOnFailFlagCount; - config->fwdOnFail = true; - break; - - case 'h': - printHelp(argv[0]); - dhcpOpt_destroyList(dhcpOptList); - conf_destroy(config); - exit(EXIT_SUCCESS); - break; - - case 'i': - ++iFlagCount; - config->ignoreExistOpt = true; - break; - - case 'p': - ++pFlagCount; - if (pFlagCount > 1) - break; - { - const char *src = optarg ? optarg : defaultPIDFilePath; - size_t pidFilePathLen = strlen(src); - config->pidFile = malloc(pidFilePathLen + 1); - if (!config->pidFile) - { - fputs("Could not allocate space for PID file name\n", stderr); - exit(EXIT_FAILURE); - } - strcpy(config->pidFile, src); - } - break; - - case 'o': - ++oFlagCount; - addDHCPOption(dhcpOptList, optarg); - break; - - case 'q': - ++qFlagCount; - if (!optarg || parseQueueNum(optarg, &config->queue)) - { - fprintf(stderr, "Invalid queue number: %s\n", optarg); - printUsage(argv[0]); - exit(EXIT_FAILURE); - } - break; - - case 'r': - ++rFlagCount; - config->removeExistOpt = true; - break; - - case 'v': - printVersion(argv[0]); - dhcpOpt_destroyList(dhcpOptList); - conf_destroy(config); - exit(EXIT_SUCCESS); - break; - - default: - printUsage(argv[0]); - exit(EXIT_FAILURE); + /* Look for the option in the option list: */ + if (optVal == options[option].val) + { + parseOption(config, option, optarg, SOURCE_CMD_LINE); break; + } + } + /* The option was not found and is invalid: */ + if (option == OPT_COUNT) + { + printUsage(); + exit(EXIT_FAILURE); } } - if ( - dFlagCount > 1 || - fFlagCount > 1 || - fwdOnFailFlagCount > 1 || - iFlagCount > 1 || - pFlagCount > 1 || - qFlagCount > 1 || - rFlagCount > 1 - ) - { - fputs("More than one option of a kind (not -o) was provided\n", stderr); - printUsage(argv[0]); - exit(EXIT_FAILURE); - } + /* If a config file path was not specified on the command line load the + * default file, but do not complain if it does not exist: */ + if (!optionCount[SOURCE_CMD_LINE][OPT_CONF_FILE]) + parseConfFile(config, defaultConfFilePath, PARSE_ALLOW_NOEXIST); - if (!qFlagCount) - { - fputs("Queue number required\n", stderr); - printUsage(argv[0]); - exit(EXIT_FAILURE); - } + validateOptionCombinations(); - if (!oFlagCount) + /* dhcpoptinj does not accept any arguments, only options: */ + if (argc - optind > 0) { - fputs("At least one DHCP option is required\n", stderr); - printUsage(argv[0]); - exit(EXIT_FAILURE); - } + fputs("No non-option arguments expected, but the following was passed: ", stderr); + for (int i = optind; i < argc; ++i) + fprintf(stderr, "\"%s\"%s", argv[i], i == argc - 1 ? "\n" : ", "); - if (iFlagCount && rFlagCount) - { - fputs("Both -i and -r cannot be used at the same time\n", stderr); - printUsage(argv[0]); + printUsage(); exit(EXIT_FAILURE); } - if (argc - optind > 0) + /* If no DHCP options were passed on the command line use the options from + * the configuration file: */ + struct DHCPOptList *dhcpOptList = dhcpOpt_count(cmdDHCPOptList) ? + cmdDHCPOptList : fileDHCPOptList; + /* Add obligatory DHCP end option and serialise options: */ + if (dhcpOpt_serialise(dhcpOptList, &config->dhcpOpts, &config->dhcpOptsSize)) { - fputs("No non-option arguments expected\n", stderr); - printUsage(argv[0]); + fputs("Failed to create DHCP option list\n", stderr); exit(EXIT_FAILURE); } - - if (oFlagCount) + /* Create an array of just the DHCP option codes: */ + if (dhcpOpt_optCodes(dhcpOptList, &config->dhcpOptCodes, &config->dhcpOptCodeCount)) { - /* Add obligatory DHCP end option and serialise options: */ - if (dhcpOpt_serialise(dhcpOptList, &config->dhcpOpts, &config->dhcpOptsSize)) - { - fputs("Failed to create DHCP option list\n", stderr); - exit(EXIT_FAILURE); - } - /* Create an array of just the DHCP option codes: */ - if (dhcpOpt_optCodes(dhcpOptList, &config->dhcpOptCodes, &config->dhcpOptCodeCount)) - { - fputs("Failed to create DHCP option code list\n", stderr); - exit(EXIT_FAILURE); - } - - dhcpOpt_destroyList(dhcpOptList); + fputs("Failed to create DHCP option code list\n", stderr); + exit(EXIT_FAILURE); } + dhcpOpt_destroyList(cmdDHCPOptList); + dhcpOpt_destroyList(fileDHCPOptList); return config; } @@ -250,25 +221,27 @@ static struct Config *createDefaultConfig(void) return config; } -static void printUsage(const char *programName) +static void printUsage(void) { - int progNameLen = (int)strlen(programName); + int progNameLen = (int)sizeof(programName) - 1; + printVersion(); printf( - "%s – DHCP option injector\n" + "\n" "Usage: %s [-df] [--forward-on-fail] [-i|-r] [-p [pid_file]] \n" + " %*s [-c config_file]\n" " %*s -q queue_num -o dhcp_option [(-o dhcp_option) ...]\n" " %s -h|-v\n" , programName, - programName, + progNameLen, "", progNameLen, "", programName ); } -static void printHelp(const char *programName) +static void printHelp(void) { - printUsage(programName); + printUsage(); printf( "\n" "%s takes a packet from a netfilter queue, ensures that it is a\n" @@ -288,6 +261,8 @@ static void printHelp(const char *programName) "\n" "Options:\n" "\n" + " -c, --conf-file [file] Specify a different configuration file,\n" + " or skip loading one altogether\n" " -d, --debug Make %s tell you as much as possible\n" " about what it does and tries to do\n" " -f, --foreground Prevent %s from running in the\n" @@ -299,12 +274,12 @@ static void printHelp(const char *programName) " -h, --help Print this help text\n" " -i, --ignore-existing-opt Proceed if an injected option already exists\n" " in the original packet. Unless\n" - " --remove-exisiting-opt is provided, the\n" + " --remove-existing-opt is provided, the\n" " default behaviour is to drop the packet\n" " -o, --option dhcp_option DHCP option to inject as a hex string,\n" " where the first byte indicates the option\n" " code. The option length field is automatically\n" - " calculatad and must be omitted. Several\n" + " calculated and must be omitted. Several\n" " options may be injected\n" " -p, --pid-file [file] Write PID to file, using specified path\n" " or a default sensible location\n" @@ -312,6 +287,20 @@ static void printHelp(const char *programName) " -r, --remove-existing-opt Remove existing DHCP options of the same\n" " kind as those to be injected\n" " -v, --version Display version\n" + , + programName, + programName, + programName, + programName, + programName, + programName); + printf( + "\n" + "%s will read %s (or the file specified with\n" + "--conf-file) for options, specified as long option names with values\n" + "separated by \"=\". \"conf-file\" is forbidden in a configuration file.\n" + "Options passed on the command line will override options in the\n" + "configuration file\n" "\n" "All the DHCP options specified with the -o/--option flag will be\n" "added before the terminating option (end option, 255). The packet is\n" @@ -349,16 +338,21 @@ static void printHelp(const char *programName) "Be good and leave packets alone.\n" , programName, - programName, - programName, - programName, - programName, - programName); + defaultConfFilePath); } -static void printVersion(const char *programName) +static void printVersion(void) { - printf("%s – DHCP option injector, version %s\n", programName, DHCPOPTINJ_VERSION); + printf( + "%s - DHCP option injector, version %s\n" + "Copyright (C) 2015-2019 by Andreas Misje\n" + "\n" + "%s comes with ABSOLUTELY NO WARRANTY. This is free software,\n" + "and you are welcome to redistribute it under certain conditions. See\n" + "the GNU General Public Licence for details.\n", + programName, + DHCPOPTINJ_VERSION, + programName); } static int parseQueueNum(const char *string, uint16_t *queueNum) @@ -406,7 +400,7 @@ static void addDHCPOption(struct DHCPOptList *list, const char *string) { if (length < 2) { - fprintf(stderr, "DHCP option string too short (payload expected): %s\n", + fprintf(stderr, "The DHCP option string is too short (payload expected): %s\n", string); exit(EXIT_FAILURE); } @@ -418,3 +412,242 @@ static void addDHCPOption(struct DHCPOptList *list, const char *string) exit(EXIT_FAILURE); } } + +static void parseConfFile(struct Config *config, const char *filePath, int parseOpts) +{ + FILE *file = fopen(filePath, "r"); + if (!file && (parseOpts & PARSE_REQUIRE_EXIST)) + { + fprintf(stderr, "Failed to open configuration file \"%s\": %s\n", + filePath, strerror(errno)); + exit(EXIT_FAILURE); + } + else if (!file) + return; + + printf("Parsing configuration file \"%s\"\n", filePath); + + unsigned int lineNo = 0; + char line[1024]; + while (fgets(line, sizeof(line), file)) + { + ++lineNo; + { + /* If the comment character '#' is found, terminate the string at + * this position: */ + char *commentStart = strchr(line, '#'); + if (commentStart) + *commentStart = '\0'; + } + char *key = line; + /* Keywords and values are separated by '=': */ + char *value = strchr(line, '='); + /* Ensure that the "value" pointer is not at the end of the buffer, + * since we aim to access data past it: */ + if (value && value - key < (ptrdiff_t)(sizeof(line) - 1)) + { + *value = '\0'; + ++value; + value = trim(value); + } + key = trim(key); + /* Line is a comment. Do not parse: */ + if (!*key) + continue; + + int option = parseKeyValue(key, value, filePath, lineNo); + parseOption(config, option, value, SOURCE_FILE); + } + + fclose(file); +} + +static void parseOption(struct Config *config, int option, char *arg, enum Source source) +{ + /* Do not override command line options from configuration file: */ + if (source == SOURCE_FILE && optionCount[SOURCE_CMD_LINE][option]) + return; + + ++optionCount[source][option]; + switch (option) + { + case OPT_CONF_FILE: + /* An empty argument is allowed, in which case no file is ever loaded + * (including the default one), so do nothing now that optionCount + * has been incremented: */ + if (arg) + parseConfFile(config, arg, PARSE_REQUIRE_EXIST); + + break; + + case OPT_DEBUG: + config->debug = true; + break; + + case OPT_FOREGROUND: + config->foreground = true; + break; + + case OPT_FORWARD_ON_FAIL: + config->fwdOnFail = true; + break; + + case OPT_HELP: + if (source == SOURCE_FILE) + { + fprintf(stderr, "The option \"%s\" doesn't make sense in a configuration " + "file\n", options[option].name); + exit(EXIT_FAILURE); + } + printHelp(); + dhcpOpt_destroyList(cmdDHCPOptList); + dhcpOpt_destroyList(fileDHCPOptList); + conf_destroy(config); + exit(EXIT_SUCCESS); + break; + + case OPT_IGNORE_EXISTING_OPT: + config->ignoreExistOpt = true; + break; + + case OPT_PID_FILE: + if (config->pidFile) + break; + { + const char *src = arg ? arg : defaultPIDFilePath; + size_t pidFilePathLen = strlen(src); + config->pidFile = malloc(pidFilePathLen + 1); + if (!config->pidFile) + { + fputs("Could not allocate space for PID file name\n", stderr); + exit(EXIT_FAILURE); + } + strcpy(config->pidFile, src); + } + break; + + case OPT_OPTION: + addDHCPOption(source == SOURCE_FILE ? fileDHCPOptList : cmdDHCPOptList, arg); + break; + + case OPT_QUEUE: + if (!arg || parseQueueNum(arg, &config->queue)) + { + fprintf(stderr, "Invalid queue number: %s\n", arg); + printUsage(); + exit(EXIT_FAILURE); + } + break; + + case OPT_REMOVE_EXISTING_OPT: + config->removeExistOpt = true; + break; + + case OPT_VERSION: + if (source == SOURCE_FILE) + { + fprintf(stderr, "The option \"%s\" doesn't make sense in a configuration " + "file\n", options[option].name); + exit(EXIT_FAILURE); + } + printVersion(); + dhcpOpt_destroyList(cmdDHCPOptList); + dhcpOpt_destroyList(fileDHCPOptList); + conf_destroy(config); + exit(EXIT_SUCCESS); + break; + + default: + /* Only valid options are passed to this function */ + break; + } +} + +static void validateOptionCombinations(void) +{ + for (size_t source = 0; source < sizeof(sources)/sizeof(sources[0]); ++source) + for (size_t option = 0; option < OPT_COUNT; ++option) + /* If an option other than --option is passed more than once, freak out: */ + if (optionCount[source][option] > 1 && option != OPT_OPTION) + { + fprintf(stderr, "%s%s can only be passed once\n", + source == SOURCE_CMD_LINE ? "Option --" : "Keyword ", + options[option].name); + printUsage(); + exit(EXIT_FAILURE); + } + + if (!totalOptionCount(OPT_QUEUE)) + { + fputs("Queue number required\n", stderr); + printUsage(); + exit(EXIT_FAILURE); + } + + if (!totalOptionCount(OPT_OPTION)) + { + fputs("At least one DHCP option is required\n", stderr); + printUsage(); + exit(EXIT_FAILURE); + } + + if (totalOptionCount(OPT_IGNORE_EXISTING_OPT) && totalOptionCount( + OPT_REMOVE_EXISTING_OPT)) + { + fprintf(stderr, "Both %s%s and %s%s cannot be used at the same time\n", + optionCount[SOURCE_CMD_LINE][OPT_IGNORE_EXISTING_OPT] ? "--" : "", + options[OPT_IGNORE_EXISTING_OPT].name, + optionCount[SOURCE_CMD_LINE][OPT_REMOVE_EXISTING_OPT] ? "--" : "", + options[OPT_REMOVE_EXISTING_OPT].name); + printUsage(); + exit(EXIT_FAILURE); + } +} + +static unsigned int totalOptionCount(int option) +{ + return optionCount[SOURCE_CMD_LINE][option] + optionCount[SOURCE_FILE][option]; +} + +static char *trim(char *text) +{ + if (!*text) + return text; + + /* Trim leading and trailing whitespace and quote characters: */ + for (char *ch = text + strlen(text) - 1; + isspace((int)*ch) || *ch == '\'' || *ch == '\"'; *ch-- = '\0'); + for (; isspace((int)*text) || *text == '\'' || *text == '\"'; *text++ = '\0'); + + return text; +} + +static int parseKeyValue(const char *key, const char *value, const char *filePath, + unsigned lineNo) +{ + for (int option = 0; option < OPT_COUNT; ++option) + { + if (strcmp(key, options[option].name)) + continue; + + if (options[option].has_arg && !value) + { + fprintf(stderr, "Failed to parse \"%s\" at line %u: %s requires an argument\n", + filePath, lineNo, options[option].name); + exit(EXIT_FAILURE); + } + else if (!options[option].has_arg && value) + { + fprintf(stderr, "Failed to parse \"%s\" at line %u: %s does not take an argument\n", + filePath, lineNo, options[option].name); + exit(EXIT_FAILURE); + } + + return option; + } + + fprintf(stderr, "Failed to parse \"%s\" at line %u: \"%s\" is not a valid keyword\n", + filePath, lineNo, key); + exit(EXIT_FAILURE); + return -1; +} diff --git a/src/config.h b/src/config.h index 49ca132..1c99550 100644 --- a/src/config.h +++ b/src/config.h @@ -53,7 +53,7 @@ struct Config bool fwdOnFail; }; -struct Config *conf_parseOpts(int argc, char **argv); +struct Config *conf_parseOpts(int argc, char * const *argv); void conf_destroy(struct Config *config); #endif // DHCPOPTINJ_CONFIG_H diff --git a/src/dhcpoptinj.c b/src/dhcpoptinj.c index ffafdc1..07b09ac 100644 --- a/src/dhcpoptinj.c +++ b/src/dhcpoptinj.c @@ -584,7 +584,7 @@ static void debugLogOptions(void) if (!config->debug) return; - logMessage(LOG_DEBUG, "%u DHCP option(s) to inject (total of %zu bytes): ", + logMessage(LOG_DEBUG, "%u DHCP option(s) to inject (with a total of %zu bytes): ", config->dhcpOptCodeCount, config->dhcpOptsSize); for (size_t i = 0; i < config->dhcpOptCodeCount; ++i) @@ -595,6 +595,8 @@ static void debugLogOptions(void) logMessage(LOG_DEBUG, "%u (0x%02X) (%s)%s", code, code, dhcp_optionString( code), delim); } + logMessage(LOG_DEBUG, "Existing options will be %s\n", config->removeExistOpt ? + "removed" : "left in place"); } static void inspectOptions(void) diff --git a/src/options.c b/src/options.c index 03b1be9..dec20f9 100644 --- a/src/options.c +++ b/src/options.c @@ -84,6 +84,11 @@ int dhcpOpt_add(struct DHCPOptList *list, int code, const void *data, size_t siz return 0; } +size_t dhcpOpt_count(struct DHCPOptList *list) +{ + return list->count; +} + int dhcpOpt_serialise(const struct DHCPOptList *list, uint8_t **buffer, size_t *size) { *size = totalSize(list); diff --git a/src/options.h b/src/options.h index c8e3739..85fad9f 100644 --- a/src/options.h +++ b/src/options.h @@ -35,6 +35,7 @@ struct DHCPOptList *dhcpOpt_createList(void); void dhcpOpt_destroyList(struct DHCPOptList *list); bool dhcpOpt_optExists(const struct DHCPOptList *list, int code); int dhcpOpt_add(struct DHCPOptList *list, int code, const void *data, size_t size); +size_t dhcpOpt_count(struct DHCPOptList *list); /* Serialise option list to an array (code + length + payload) */ int dhcpOpt_serialise(const struct DHCPOptList *list, uint8_t **buffer, size_t *size); /* Create an array containg the integer codes of all the DHCP options in the From c4af6386ad5e7560a73c415022707713c0cde0e7 Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Sat, 6 Apr 2019 21:30:19 +0200 Subject: [PATCH 03/15] Update README --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 5d789b4..53063d7 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,39 @@ use a build directory, you can get rid of all the cmake rubbish by running `git clean -dfx`. Note, however, that this removes **everything** in the project directory that is not under source control. +## Configuration file + +dhcptopinj will attempt to parse /etc/dhcpoptinj.conf or the file passed with +-c/--conf-file. The syntax of the configuration file is +* **key=value**, where *key* is the long option name, or +* **key** if the option does not take an argument + +Whitespace is optional. Anything after and including the character **#** is +considered a comment. DHCP options are listed one-by-one as *option=01:02:03*. +Quotes around the option hex string is optional, and the bytes may be separated +by any number of non-hexadecimal characters. + +The options *version*, *help* and *conf-file* are not accepted in a +configuration file. + +Example: +```conf +# Run in foreground: +foreground +# Enable debug output: +debug +# Override hostname to "fjasehost": +option = '0C 66 6A 61 73 65 68 6F 73 74' +# Send agent ID "Fjas": +option = "52:01:04:46:6A:61:73" +# Override address request to ask for 10.20.30.40: +option=320A141E28 +# Use queue 12: +queue = 12 + +remove-existing-opt # Remove options before inserting +``` + ## Help This readme should have got you started. There is no man page for dhcpoptinj, From 2f99c98af74efeb14719b4b9d2e51b61216577b1 Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Sun, 7 Apr 2019 00:31:43 +0200 Subject: [PATCH 04/15] Update version and changelog --- CHANGELOG.md | 5 +++++ CMakeLists.txt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70265a6..04abbe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.5.0 - 2019-04-19 +### Added +- Parse configuration from file. +- Add copyright to usage output. + ## 0.4.4 - 2019-03-25 ### Fixed - Update version number in binary. diff --git a/CMakeLists.txt b/CMakeLists.txt index b0b40d8..c0ef426 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ else() set(CMAKE_INSTALL_PREFIX /usr CACHE STRING "Choose install prefix") endif() -project(dhcpoptinj LANGUAGES C VERSION 0.4.4) +project(dhcpoptinj LANGUAGES C VERSION 0.5.0) add_definitions(-DDHCPOPTINJ_VERSION="${PROJECT_VERSION}") set(SOURCES src/config.c From a504f6b5a3013b344bb8c2c236dca39090e81f56 Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 9 Apr 2019 19:47:55 +0200 Subject: [PATCH 05/15] Add a comment describing the __format__ attribute introduced --- src/dhcpoptinj.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dhcpoptinj.c b/src/dhcpoptinj.c index ffafdc1..2df10be 100644 --- a/src/dhcpoptinj.c +++ b/src/dhcpoptinj.c @@ -482,6 +482,8 @@ static enum MangleResult mangleOptions(const uint8_t *origData, size_t origDataS return Mangle_OK; } +/* Instruct clang that "format" is a printf-style format parameter to avoid + * non-literal format string warnings in clang: */ __attribute__((__format__ (__printf__, 2, 0))) static void logMessage(int priority, const char *format, ...) { From 43e8e5a3344b5213f202ee46288848f7065ffcdb Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 9 Apr 2019 20:07:09 +0200 Subject: [PATCH 06/15] Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04abbe5..e56eb56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Parse configuration from file. - Add copyright to usage output. +### Fixed +- Fix pedantic errors from clang. + ## 0.4.4 - 2019-03-25 ### Fixed - Update version number in binary. From 137069a95db2d308a9477a39dbf001c231366c1c Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 9 Apr 2019 20:07:15 +0200 Subject: [PATCH 07/15] Update date in copyright secion in source files --- src/config.c | 2 +- src/config.h | 2 +- src/dhcp.c | 2 +- src/dhcp.h | 2 +- src/dhcpoptinj.c | 2 +- src/ipv4.c | 2 +- src/ipv4.h | 2 +- src/options.c | 2 +- src/options.h | 2 +- src/udp.h | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/config.c b/src/config.c index fcc7d30..c38507c 100644 --- a/src/config.c +++ b/src/config.c @@ -1,5 +1,5 @@ /* - * Copyright © 2015 Andreas Misje + * Copyright © 2015–2019 Andreas Misje * * This file is part of dhcpoptinj. * diff --git a/src/config.h b/src/config.h index 1c99550..11e0752 100644 --- a/src/config.h +++ b/src/config.h @@ -1,5 +1,5 @@ /* - * Copyright © 2015 Andreas Misje + * Copyright © 2015–2019 Andreas Misje * * This file is part of dhcpoptinj. * diff --git a/src/dhcp.c b/src/dhcp.c index 0635e8c..700c9cf 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -1,5 +1,5 @@ /* - * Copyright © 2015 Andreas Misje + * Copyright © 2015–2019 Andreas Misje * * This file is part of dhcpoptinj. * diff --git a/src/dhcp.h b/src/dhcp.h index 7e5923b..ae0439d 100644 --- a/src/dhcp.h +++ b/src/dhcp.h @@ -1,5 +1,5 @@ /* - * Copyright © 2015 Andreas Misje + * Copyright © 2015–2019 Andreas Misje * * This file is part of dhcpoptinj. * diff --git a/src/dhcpoptinj.c b/src/dhcpoptinj.c index 98e2145..b0c9f8a 100644 --- a/src/dhcpoptinj.c +++ b/src/dhcpoptinj.c @@ -1,5 +1,5 @@ /* - * Copyright © 2015 Andreas Misje + * Copyright © 2015–2019 Andreas Misje * * This file is part of dhcpoptinj. * diff --git a/src/ipv4.c b/src/ipv4.c index 55b0e35..387d315 100644 --- a/src/ipv4.c +++ b/src/ipv4.c @@ -1,5 +1,5 @@ /* - * Copyright © 2015 Andreas Misje + * Copyright © 2015–2019 Andreas Misje * * This file is part of dhcpoptinj. * diff --git a/src/ipv4.h b/src/ipv4.h index 00141b4..f02c517 100644 --- a/src/ipv4.h +++ b/src/ipv4.h @@ -1,5 +1,5 @@ /* - * Copyright © 2015 Andreas Misje + * Copyright © 2015–2019 Andreas Misje * * This file is part of dhcpoptinj. * diff --git a/src/options.c b/src/options.c index dec20f9..c23c887 100644 --- a/src/options.c +++ b/src/options.c @@ -1,5 +1,5 @@ /* - * Copyright © 2015 Andreas Misje + * Copyright © 2015–2019 Andreas Misje * * This file is part of dhcpoptinj. * diff --git a/src/options.h b/src/options.h index 85fad9f..a579250 100644 --- a/src/options.h +++ b/src/options.h @@ -1,5 +1,5 @@ /* - * Copyright © 2015 Andreas Misje + * Copyright © 2015–2019 Andreas Misje * * This file is part of dhcpoptinj. * diff --git a/src/udp.h b/src/udp.h index d3e98c2..7406498 100644 --- a/src/udp.h +++ b/src/udp.h @@ -1,5 +1,5 @@ /* - * Copyright © 2015 Andreas Misje + * Copyright © 2015–2019 Andreas Misje * * This file is part of dhcpoptinj. * From de6b37c69b1f020e83193b2e980916cf00b52573 Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 16 Apr 2019 19:12:36 +0200 Subject: [PATCH 08/15] Improve error message wording --- src/config.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index c38507c..19be5a2 100644 --- a/src/config.c +++ b/src/config.c @@ -570,8 +570,9 @@ static void validateOptionCombinations(void) /* If an option other than --option is passed more than once, freak out: */ if (optionCount[source][option] > 1 && option != OPT_OPTION) { - fprintf(stderr, "%s%s can only be passed once\n", + fprintf(stderr, "%s%s can only be %s once\n", source == SOURCE_CMD_LINE ? "Option --" : "Keyword ", + source == SOURCE_CMD_LINE ? "passed" : "specified", options[option].name); printUsage(); exit(EXIT_FAILURE); From 8f4186df676d10fd74e2f034f20538d973a68673 Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 16 Apr 2019 19:13:18 +0200 Subject: [PATCH 09/15] Fix bug: Support optional arguments in config file --- src/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index 19be5a2..de9d8b2 100644 --- a/src/config.c +++ b/src/config.c @@ -631,7 +631,7 @@ static int parseKeyValue(const char *key, const char *value, const char *filePat if (strcmp(key, options[option].name)) continue; - if (options[option].has_arg && !value) + if (options[option].has_arg == required_argument && !value) { fprintf(stderr, "Failed to parse \"%s\" at line %u: %s requires an argument\n", filePath, lineNo, options[option].name); From 0c46425fb2ebdd4c88a0d6a2b2f60bd6030654a7 Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 16 Apr 2019 19:13:59 +0200 Subject: [PATCH 10/15] Fix date in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e56eb56..bd18ddb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -## 0.5.0 - 2019-04-19 +## 0.5.0 - 2019-04-09 ### Added - Parse configuration from file. - Add copyright to usage output. From 12fdaa34703d0d989beea91a7053aa9e42d74601 Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 16 Apr 2019 19:19:03 +0200 Subject: [PATCH 11/15] Use 1-byte alignment on DHCPOption struct --- src/dhcp.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dhcp.h b/src/dhcp.h index ae0439d..5908663 100644 --- a/src/dhcp.h +++ b/src/dhcp.h @@ -47,7 +47,9 @@ struct BootP uint32_t cookie; // options … }; +#pragma pack() +#pragma pack(1) struct DHCPOption { uint8_t code; From 8da9239d5a6787485ef33bd8f6184b526b85228a Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 16 Apr 2019 19:19:14 +0200 Subject: [PATCH 12/15] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd18ddb..0803467 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Changed +- Use 1-byte alignment on DHCP options. + +### Fixed +- Allow optional values to configuration file keywords (corretly support + "pid-file" as on the command line). ## 0.5.0 - 2019-04-09 ### Added From 40cc6fc43835e6b0a916b0a6574c409953f7aa5b Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 16 Apr 2019 19:35:03 +0200 Subject: [PATCH 13/15] Mention deb package and remove reference to man page and bash comp. --- CHANGELOG.md | 1 + README.md | 20 +++++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0803467..33e3f8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Changed - Use 1-byte alignment on DHCP options. +- Refer to salsa.debian.org for deb package source. ### Fixed - Allow optional values to configuration file keywords (corretly support diff --git a/README.md b/README.md index 53063d7..77983a1 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,12 @@ this option as the last option automatically. ## Installing -dhcoptinj is quite a simple program and should be unproblematic to build. +dhcpoptinj is submitted to Debian and will hopefully make it to unstable (and +consequently testing and stable) in not too long. The deb package is under +source control at [salsa](https://salsa.debian.org/misje-guest/dhcpoptinj). +Installing dhcpoptinj from the deb package is recommended over the following +manual installation procedure, because it also includes a man page, bash +completion rules, example files etc. ### Prerequisites @@ -99,13 +104,6 @@ install cmake libnetfilter-queue-dev`. 1. Install (optional, but you will benefit from having dhcpoptinj in your PATH): `sudo make install` -The makefile does not install the man page (doc/dhcpoptinj.8) nor the bash -completion file (debian/dhcpoptinj.bash-completion). Debhelper does such a good -job of making sure that these files are installed correctly, making sure the -completions work instantly and that the man-db is updated. I have no intention -of duplicating this installation logic, so please use the deb package if you -want these extra files. - ### Demolish 1. Run `sudo make uninstall` from your build directory @@ -150,9 +148,9 @@ remove-existing-opt # Remove options before inserting ## Help -This readme should have got you started. There is no man page for dhcpoptinj, -but the help (`dhcpoptinj -h`) should cover everything the utility has to -offer. +This readme should have got you started. Also check out the man page (in the +deb package) and the help output (`dhcpoptinj -h`), which should cover +everything the utility has to offer. For bugs and suggestions please create an issue. From 95808b5d5e248ccb24c5a0b595abdfd96ebcb30b Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 16 Apr 2019 19:43:01 +0200 Subject: [PATCH 14/15] Remove old reference to bug in README --- CHANGELOG.md | 1 + README.md | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33e3f8f..5dfda99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Use 1-byte alignment on DHCP options. - Refer to salsa.debian.org for deb package source. +- Remove old bug reference in README. ### Fixed - Allow optional values to configuration file keywords (corretly support diff --git a/README.md b/README.md index 77983a1..3c2d4d5 100644 --- a/README.md +++ b/README.md @@ -173,14 +173,6 @@ following are missing features that hopefully will be added some day: ### Known issues -I am not experienced in the netfilter library. There may be (although I cannot -promise) bugs. - -1. *Syscall param socketcall.sendto(msg) points to uninitialised byte(s)* - valgrind error - - This issue is not fully investigated yet. - 1. Memory leak on non-normal exit. This is not considered a leak. However, there should be no memory leak on a From 19b1fa5112853d7fe0d3b4b2ba2f367d53825cd8 Mon Sep 17 00:00:00 2001 From: Andreas Misje Date: Tue, 16 Apr 2019 20:05:45 +0200 Subject: [PATCH 15/15] Update version and changelog --- CHANGELOG.md | 2 ++ CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dfda99..319c19a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased + +## 0.5.1 - 2019-04-16 ### Changed - Use 1-byte alignment on DHCP options. - Refer to salsa.debian.org for deb package source. diff --git a/CMakeLists.txt b/CMakeLists.txt index c0ef426..fe439f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,7 @@ else() set(CMAKE_INSTALL_PREFIX /usr CACHE STRING "Choose install prefix") endif() -project(dhcpoptinj LANGUAGES C VERSION 0.5.0) +project(dhcpoptinj LANGUAGES C VERSION 0.5.1) add_definitions(-DDHCPOPTINJ_VERSION="${PROJECT_VERSION}") set(SOURCES src/config.c