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:
M | udyfi.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: "