udyfi

Small client for dy.fi (and possibly other services) DNS renewals
git clone https://git.inz.fi/udyfi/
Log | Files | Refs

commit bf7f4ef13d96ab28c62f790929e5aa227b4e8d0f
parent 80a965952f3fcb512da472186846766b66ddba6e
Author: Santtu Lakkala <inz@inz.fi>
Date:   Tue,  4 Jul 2023 13:10:54 +0300

Add simple configuration file

Allow reading config options from a file; the format it the same as
command line options.

Diffstat:
Mudyfi.c | 295++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
1 file changed, 224 insertions(+), 71 deletions(-)

diff --git a/udyfi.c b/udyfi.c @@ -33,6 +33,7 @@ #include <string.h> #include <stdio.h> #include <errno.h> +#include <pwd.h> #include <stdint.h> #include <stdlib.h> #include <stdbool.h> @@ -72,6 +73,57 @@ char *argv0; #define IF_SSL(...) #endif +static char **strsplit(char *s, int *c) +{ + size_t n = 1; + char *r = s; + char *w = s; + char **rv = malloc((n + 1) * sizeof(*rv)); + enum { NONE, SQUOT = '\'', DQUOT = '"' } state = NONE; + + rv[0] = s; + + while (*r) { + switch (*r) { + case ' ': + if (state == NONE) { + *w++ = '\0'; + r++; + while (*r == ' ') + r++; + if (!*r) + continue; + rv = realloc(rv, (++n + 1) * sizeof(*rv)); + rv[n - 1] = w; + continue; + } + break; + + case '\'': + case '"': + if (state == NONE) + state = *r; + else if ((char)state == *r) + state = NONE; + else + break; + + r++; + continue; + + case '\\': + if (state != SQUOT) + r++; + break; + } + *w++ = *r++; + } + *w = '\0'; + rv[n] = NULL; + *c = n; + return rv; +} + static void sig_handler(int signum) { (void)signum; @@ -465,10 +517,12 @@ static void usage(void) { "interface/" #endif "local address>] " + "[-U <username>] " "[-c <checkip server>] " "[-C <checkip port>] " "[-d <dyndns server>] " "[-D <dyndns port>] " + "[-F <config file>] " "[-b] " "[-P <pidfile>] " IF_SSL( @@ -510,6 +564,8 @@ int main(int argc, char **argv) const char *username = NULL; const char *pidfile = NULL; const char *iface = NULL; + const char *dropuser = NULL; + const char *domains = NULL; char password[256] = ""; int fd; time_t next_refresh = 0; @@ -522,6 +578,13 @@ int main(int argc, char **argv) int ret = EXIT_SUCCESS; FILE *log = stderr; const char *logfile = NULL; + char *arg; + + struct { + char **argv; + int argc; + char *arg; + } cfgfile = { 0 }; #ifdef USE_LIBTLS int port_set = 0; @@ -531,78 +594,152 @@ int main(int argc, char **argv) struct tls *ctx = NULL; #endif - ARGBEGIN { - case 'i': - iface = EARGF(usage()); - break; - case 'u': - username = EARGF(usage()); - break; - case 'p': { - char *pass = EARGF(usage()); - snprintf(password, sizeof(password), - "%s", pass); - memset(pass, 0, strlen(pass)); - break; - } - case 'f': - if ((fd = open(EARGF(usage()), O_RDONLY)) >= 0) { - int r = read(fd, password, sizeof(password) - 1); - close(fd); - - if (r < 0) { - fprintf(stderr, "Failed to read password from file"); - return EXIT_FAILURE; - } + for (argv0 = *argv, argv++, argc--; + argc && argv[0][0] == '-' && argv[0][1]; + argc--, argv++) { + arg = &argv[0][1]; - while (r && (password[r - 1] == '\n' || password[r - 1] == '\r')) - r--; - password[r] = '\0'; - } else { - fprintf(stderr, "Failed to open password file\n"); - return EXIT_FAILURE; +cfgtoggle: + if (argv[0][1] == '-' && argv[0][1] == '\0') { + argv++; + argc--; + break; } - break; - case 'c': - checkip_service = EARGF(usage()); - break; - case '4': - af = AF_INET; - break; - case '6': - af = AF_INET6; - break; - case 'C': - checkip_port = EARGF(usage()); - IF_SSL(check_port_set = 1;) - break; - case 'd': - dyndns_service = EARGF(usage()); - break; - case 'D': - dyndns_port = EARGF(usage()); - IF_SSL(port_set = 1;) - break; - case 'b': - background = 1; - break; - case 'P': - pidfile = EARGF(usage()); - break; - case 'l': - logfile = EARGF(usage()); - break; + + while (*arg) { + switch (*arg++) { + case 'i': + iface = EARGF(usage()); + break; + case 'u': + username = EARGF(usage()); + break; + case 'U': + dropuser = EARGF(usage()); + break; + case 'p': { + char *pass = EARGF(usage()); + snprintf(password, sizeof(password), + "%s", pass); + memset(pass, 0, strlen(pass)); + break; + } + case 'F': { + FILE *f; + char *data = NULL; + size_t n = 0; + ssize_t r; + + if (cfgfile.argv) { + fprintf(stderr, "Nested configuration files not supported\n"); + return EXIT_FAILURE; + } + if (!(f = fopen(EARGF(usage()), "r"))) { + fprintf(stderr, "Failed to open configuration file\n"); + return EXIT_FAILURE; + } + + r = getline(&data, &n, f); + fclose(f); + + if (r < 0) { + fprintf(stderr, "Failed to read configuration file\n"); + return EXIT_FAILURE; + } + + while (r && (data[r - 1] == '\r' || data[r - 1] == '\n')) + r--; + data[r] = '\0'; + + if (!r) { + free(data); + break; + } + + cfgfile.argv = argv; + cfgfile.argc = argc; + cfgfile.arg = arg; + + argv = strsplit(data, &argc); + arg = argv[0]; + if (*arg != '-') + continue; + arg++; + goto cfgtoggle; + } + case 'f': + if ((fd = open(EARGF(usage()), O_RDONLY)) >= 0) { + int r = read(fd, password, sizeof(password) - 1); + close(fd); + + if (r < 0) { + fprintf(stderr, "Failed to read password from file\n"); + return EXIT_FAILURE; + } + + while (r && (password[r - 1] == '\n' || password[r - 1] == '\r')) + r--; + password[r] = '\0'; + } else { + fprintf(stderr, "Failed to open password file\n"); + return EXIT_FAILURE; + } + break; + case 'c': + checkip_service = EARGF(usage()); + break; + case '4': + af = AF_INET; + break; + case '6': + af = AF_INET6; + break; + case 'C': + checkip_port = EARGF(usage()); + IF_SSL(check_port_set = 1;) + break; + case 'd': + dyndns_service = EARGF(usage()); + break; + case 'D': + dyndns_port = EARGF(usage()); + IF_SSL(port_set = 1;) + break; + case 'b': + background = 1; + break; + case 'P': + pidfile = EARGF(usage()); + break; + case 'l': + logfile = EARGF(usage()); + break; #ifdef USE_LIBTLS - case 's': - use_ssl = 1; - break; - case 'S': - check_ssl = 1; - break; + case 's': + use_ssl = 1; + break; + case 'S': + check_ssl = 1; + break; #endif - default: - return EXIT_FAILURE; - } ARGEND + default: + usage(); + return EXIT_FAILURE; + } + } + } + + if (argc) + domains = argv[0]; + if (cfgfile.argv) { + argv = cfgfile.argv; + argc = cfgfile.argc; + arg = cfgfile.arg; + + cfgfile.argv = NULL; + + goto cfgtoggle; + } #if defined(USE_LIBTLS) if (use_ssl || check_ssl) { @@ -620,7 +757,7 @@ int main(int argc, char **argv) } #endif - if (argc < 1 || !username) { + if (!domains || !username) { usage(); return EXIT_FAILURE; } @@ -635,6 +772,22 @@ int main(int argc, char **argv) else if (pidfile) write_pidfile(pidfile, getpid()); + if (dropuser) { + struct passwd *ent = getpwnam(dropuser); + if (!ent) { + fprintf(log, "Failed to get user\n"); + ret = EXIT_FAILURE; + goto out; + } + + if (setgid(ent->pw_gid) || + setuid(ent->pw_uid)) { + fprintf(log, "Failed to change user\n"); + ret = EXIT_FAILURE; + goto out; + } + } + fprintf(log, "udyfi started\n"); signal(SIGINT, sig_handler); @@ -651,7 +804,7 @@ int main(int argc, char **argv) if ((strcmp(ip, prev_ip) || now > next_refresh)) { switch (update_ip(dyndns_service, dyndns_port, - *argv, + domains, username, password, iface, af IF_SSL(, use_ssl ? ctx : NULL) )) { @@ -680,7 +833,7 @@ int main(int argc, char **argv) break; case UP_GOOD: fprintf(log, "%s now point to %s\n", - *argv, ip); + domains, ip); break; case UP_DNSERR: fprintf(log, "WARNING: "