udyfi

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

commit 8adb0d2527090e48add742b8dad32a4ecf837775
parent c3fbeeeae176034b869f84d283953edaf89ecf91
Author: Santtu Lakkala <inz@inz.fi>
Date:   Wed,  5 Jul 2023 20:12:45 +0300

Refactor and add no-ip.com support

 - Handle common HTTP error codes and act appropriately
 - Add handling for no-ip.com error codes
 - Re-check IP on SIGHUP
 - Handle SIGINT and SIGTERM in any state
 - Add user-agent

Diffstat:
MMakefile | 10++++++++--
Aconfig.def.h | 18++++++++++++++++++
Mudyfi.c | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
3 files changed, 209 insertions(+), 55 deletions(-)

diff --git a/Makefile b/Makefile @@ -3,7 +3,8 @@ CFLAGS += -W -Wall -std=c99 -pedantic -D_POSIX_C_SOURCE=200809L PREFIX ?= /usr/local BINDIR := $(PREFIX)/bin IFADDRS_CFLAGS ?= -DUSE_IFADDRS -HEADERS = arg.h +HEADERS = arg.h config.h +VERSION=0.1.0 LIBTLS_CFLAGS += -DUSE_LIBTLS -I/usr/local/include LIBS := -L/usr/local/lib -ltls @@ -11,10 +12,15 @@ LIBS := -L/usr/local/lib -ltls #OPENSSL_CFLAGS += $(shell pkg-config openssl --cflags && echo -DUSE_OPENSSL) #LIBS := $(shell pkg-config openssl --libs) #HEADERS += minitls.h + all: udyfi +config.h: config.def.h + if test -f "$@"; then echo "Refusing to overwrite old config.h"; false; fi + cp $< $@ + udyfi: udyfi.c $(HEADERS) - $(CROSS)$(CC) $(CFLAGS) $(LIBTLS_CFLAGS) $(OPENSSL_CFLAGS) $(IFADDRS_CFLAGS) $< -o $@ $(LIBS) + $(CROSS)$(CC) -DVERSION='"$(VERSION)"' $(CFLAGS) $(LIBTLS_CFLAGS) $(OPENSSL_CFLAGS) $(IFADDRS_CFLAGS) $< -o $@ $(LIBS) clean: rm -f udyfi diff --git a/config.def.h b/config.def.h @@ -0,0 +1,18 @@ +static const time_t minimum_refresh = DAYS(5); +static const time_t minimum_refresh_rand = HOURS(10); + +static const time_t check_interval = MINUTES(4); +static const time_t check_interval_rand = MINUTES(2); + +static const time_t error_fallback = MINUTES(30); +static const time_t error_fallback_rand = MINUTES(5); + +static const char *checkip_service = "checkip.dy.fi"; +static const char *checkip_port = "80"; +IF_SSL(static bool checkip_ssl = false;) + +static const char *dyndns_service = "dy.fi"; +static const char *dyndns_port = "80"; +IF_SSL(static bool dyndns_ssl = true;) + +static const char *default_ua = "udyfi/" VERSION " inz@inz.fi"; diff --git a/udyfi.c b/udyfi.c @@ -25,6 +25,7 @@ #undef __BSD_VISIBLE #define __BSD_VISIBLE 1 #include <sys/socket.h> +#include <sys/select.h> #include <signal.h> #include <poll.h> #include <fcntl.h> @@ -37,6 +38,7 @@ #include <stdint.h> #include <stdlib.h> #include <stdbool.h> +#include <limits.h> #ifdef USE_IFADDRS #define __USE_MISC #include <net/if.h> @@ -52,18 +54,6 @@ #endif #include "arg.h" -static const time_t minimum_refresh = 5 * 24 * 60 * 60; -static const time_t minimum_refresh_rand = 10 * 60 * 60; - -static const time_t check_interval = 4 * 60; -static const time_t check_interval_rand = 2 * 60; - -static const char *checkip_service = "checkip.dy.fi"; -static const char *checkip_port = "80"; - -static const char *dyndns_service = "dy.fi"; -static const char *dyndns_port = "80"; - static bool running = true; char *argv0; @@ -73,6 +63,11 @@ char *argv0; #define IF_SSL(...) #endif +#define MINUTES(x) ((x) * 60) +#define HOURS(x) (MINUTES(x) * 60) +#define DAYS(x) (HOURS(x) * 24) +#include "config.h" + static char **strsplit(char *s, int *c) { size_t n = 1; @@ -138,7 +133,7 @@ static void sighup_handler(int signum) static ssize_t http_get(const char *server, const char *port, const char *path, - const char *headers, + const char * restrict headers, const char *iface, int *af, char *buffer, @@ -208,7 +203,7 @@ static ssize_t http_get(const char *server, #if USE_IFADDRS if (addrs) { for (j = addrs; j; j = j->ifa_next) { - if (!strcmp(j->ifa_name, iface) && + if (!strcmp(j->ifa_name, iface) && (((j->ifa_flags & (IFF_UP | IFF_BROADCAST | IFF_LOOPBACK)) == @@ -261,6 +256,8 @@ static ssize_t http_get(const char *server, return -1; } + printf("%s\n", buffer); + #ifdef USE_LIBTLS if (ctx) { if (tls_connect_socket(ctx, sock, server)) { @@ -282,6 +279,10 @@ static ssize_t http_get(const char *server, return -1; } + + if (!running) + return -1; + s = 0; do { #ifdef USE_LIBTLS @@ -291,10 +292,14 @@ static ssize_t http_get(const char *server, #endif r = read(sock, buffer + s, bufsize - s - 1); if (r < 0) { + if (!running) + return -1; #ifdef USE_LIBTLS if (ctx) { if (r == TLS_WANT_POLLIN) { poll(&(struct pollfd){ .fd = sock, .events = POLLIN }, 1, 0); + if (!running) + return -1; continue; } @@ -321,21 +326,49 @@ static ssize_t http_get(const char *server, return s; } +static char *add_header(char *buffer, char *buf_end, const char *name, const char *value) { + static const char sep[2] = ": "; + static const char end[2] = "\r\n"; + size_t nl; + size_t vl; + + if (!buffer) + return NULL; + + nl = strlen(name); + vl = strlen(value); + + if (buffer + nl + vl + sizeof(sep) + sizeof(end) >= buf_end) + return NULL; + buffer = (char *)memcpy(buffer, name, nl) + nl; + buffer = (char *)memcpy(buffer, sep, sizeof(sep)) + sizeof(sep); + buffer = (char *)memcpy(buffer, value, vl) + vl; + buffer = (char *)memcpy(buffer, end, sizeof(end)) + sizeof(end); + *buffer = '\0'; + + return buffer; +} + static int check_ip(const char *server, const char *port, const char *iface, int af, + const char *ua, char *ip_buffer, size_t ip_size IF_SSL(, struct tls *ctx) ) { + char header_buffer[1024]; char buffer[1024]; const char *ip; size_t iplen; + if (!add_header(header_buffer, 1[&header_buffer], "User-Agent", ua)) + return -1; + if (http_get(server, port, "/", - NULL, + header_buffer, iface, &af, buffer, @@ -344,11 +377,13 @@ static int check_ip(const char *server, ) < 0) return -1; - ip = strstr(buffer, "Current IP Address: "); - if (!ip) + if ((ip = strstr(buffer, "Current IP Address: "))) + ip += sizeof("Current IP Address: ") - 1; + else if ((ip = strstr(buffer, "\r\n\r\n"))) + ip += sizeof("\r\n\r\n") - 1; + else return -1; - ip += sizeof("Current IP Address: ") - 1; switch (af) { case AF_INET: default: @@ -360,6 +395,8 @@ static int check_ip(const char *server, } if (iplen > ip_size) return -1; + if (ip == buffer && ip[iplen]) + return -1; sprintf(ip_buffer, "%.*s", (int)iplen, ip); return 0; @@ -438,7 +475,10 @@ enum update_status { UP_GOOD, UP_DNSERR, UP_ABUSE, + UP_BADAGENT, + UP_911, UP_UNKNOWN, + UP_REDIRECT, UP_INTERNAL, UP_NETWORK }; @@ -451,52 +491,87 @@ static const char *statusstr[] = { "nochg", "good ", "dnserr", - "abuse" + "abuse", + "badagent", + "911" }; +static unsigned short http_code(const char *buffer, const char *bend) +{ + const char match[] = "HTTP/1."; + unsigned int rv = 0; + + if (buffer + sizeof(match) >= bend || memcmp(buffer, match, sizeof(match) - 1)) + return 0; + buffer += sizeof(match); + if (buffer[-1] != '0' && buffer[-1] != '1') + return 0; + if (buffer >= bend || *buffer++ != ' ') + return 0; + for (; buffer < bend; buffer++) { + if (*buffer == ' ') + break; + if (*buffer >= '0' && *buffer <= '9') + rv = rv * 10 + (*buffer - '0'); + else + return 0; + + if (rv > USHRT_MAX) + return 0; + } + + return rv; +} + static int update_ip(const char *server, const char *port, const char *hosts, - const char *username, - const char *password, + const char *auth, const char *iface, - int af + int af, + const char *ua IF_SSL(, struct tls *ctx) ) { char buffer[1024]; - char auth_buffer[1024]; char path_buffer[1024]; - int pos; + char header_buffer[1024]; int r; size_t bl; const char *body; + unsigned short code; + char *bend = 1[&header_buffer]; - pos = snprintf(auth_buffer, sizeof(auth_buffer), - "Authorization: Basic "); - if (pos >= (int)sizeof(auth_buffer)) - return UP_INTERNAL; - if ((r = auth_token(username, password, - auth_buffer + pos, sizeof(auth_buffer) - pos)) < 0) - return UP_INTERNAL; - pos += r; - pos += snprintf(auth_buffer + pos, sizeof(auth_buffer) - pos, - "\r\n"); - if (pos >= (int)sizeof(auth_buffer)) + char *bpos = add_header(header_buffer, bend, "User-Agent", ua); + bpos = add_header(bpos, bend, "Authorization", auth); + + if (!bpos) return UP_INTERNAL; - pos = snprintf(path_buffer, sizeof(path_buffer), + r = snprintf(path_buffer, sizeof(path_buffer), "/nic/update?hostname=%s", hosts); - if (pos >= (int)sizeof(path_buffer)) + if (r >= (int)sizeof(path_buffer)) return UP_INTERNAL; if ((r = http_get(server, port, path_buffer, - auth_buffer, iface, &af, + header_buffer, iface, &af, buffer, sizeof(buffer) IF_SSL(, ctx) )) < 0) return UP_NETWORK; + code = http_code(buffer, buffer + strlen(buffer)); + if (!code || code >= 500) + return UP_911; + if (code == 401) + return UP_BADAUTH; + if (code == 403) + return UP_NOHOST; + if (code > 400) + return UP_UNKNOWN; + if (code >= 300) + return UP_REDIRECT; + if ((body = find_body(buffer, r, &bl))) { for (r = 0; r < UP_UNKNOWN; r++) { @@ -533,6 +608,8 @@ static void usage(void) { IF_SSL( "[-s] " "[-S] " + "[-z] " + "[-Z] " ) "[-6] " "[-4] " @@ -571,9 +648,12 @@ int main(int argc, char **argv) const char *iface = NULL; const char *dropuser = NULL; const char *domains = NULL; + const char *ua = default_ua; char password[256] = ""; + char authbuf[1024] = "Basic "; int fd; time_t next_refresh = 0; + time_t last_error = 0; char ipbuf1[64]; char ipbuf2[64] = ""; char *ip = ipbuf1; @@ -584,6 +664,9 @@ int main(int argc, char **argv) FILE *log = stderr; const char *logfile = NULL; char *arg; + sigset_t sigset_all; + sigset_t sigset_hup; + sigset_t sigset_dfl; struct { char **argv; @@ -594,11 +677,19 @@ int main(int argc, char **argv) #ifdef USE_LIBTLS int port_set = 0; int check_port_set = 0; - int use_ssl = 0; - int check_ssl = 0; + bool use_ssl = dyndns_ssl; + bool check_ssl = checkip_ssl; struct tls *ctx = NULL; #endif + sigemptyset(&sigset_all); + sigaddset(&sigset_all, SIGHUP); + sigaddset(&sigset_all, SIGTERM); + sigaddset(&sigset_all, SIGINT); + + sigemptyset(&sigset_hup); + sigaddset(&sigset_hup, SIGHUP); + for (argv0 = *argv, argv++, argc--; argc && argv[0][0] == '-' && argv[0][1]; argc--, argv++) { @@ -613,6 +704,9 @@ cfgtoggle: while (*arg) { switch (*arg++) { + case 'a': + ua = EARGF(usage()); + break; case 'i': iface = EARGF(usage()); break; @@ -721,10 +815,16 @@ cfgtoggle: break; #ifdef USE_LIBTLS case 's': - use_ssl = 1; + use_ssl = true; break; case 'S': - check_ssl = 1; + check_ssl = true; + break; + case 'z': + use_ssl = false; + break; + case 'Z': + check_ssl = false; break; #endif default: @@ -767,6 +867,9 @@ cfgtoggle: return EXIT_FAILURE; } + auth_token(username, password, authbuf + strlen(authbuf), sizeof(authbuf) - strlen(authbuf)); + memset(password, 0, sizeof(password)); + srand(time(NULL)); if (logfile) @@ -799,10 +902,13 @@ cfgtoggle: signal(SIGTERM, sig_handler); sigaction(SIGHUP, &(struct sigaction){ .sa_handler = sighup_handler }, NULL); + sigprocmask(SIG_BLOCK, &sigset_hup, &sigset_dfl); + while (running) { time_t now = time(NULL); - if (check_ip(checkip_service, checkip_port, iface, af, + if (now >= last_error + error_fallback && + check_ip(checkip_service, checkip_port, iface, af, ua, ip, sizeof(ipbuf1) IF_SSL(, check_ssl ? ctx : NULL) ) >= 0) { @@ -811,7 +917,7 @@ cfgtoggle: if ((strcmp(ip, prev_ip) || now > next_refresh)) { switch (update_ip(dyndns_service, dyndns_port, domains, - username, password, iface, af + authbuf, iface, af, ua IF_SSL(, use_ssl ? ctx : NULL) )) { case UP_BADAUTH: @@ -841,19 +947,31 @@ cfgtoggle: fprintf(log, "%s now point to %s\n", domains, ip); break; + case UP_911: case UP_DNSERR: fprintf(log, "WARNING: " "Temporary service error\n"); - break; + last_error = now; + goto retry; case UP_ABUSE: fprintf(log, "FATAL: " "We are flagged as abuse\n"); ret = EXIT_FAILURE; goto out; + case UP_BADAGENT: + fprintf(log, "FATAL: " + "We are flagged as bad agent\n"); + ret = EXIT_FAILURE; + goto out; case UP_UNKNOWN: fprintf(log, "WARNING: " "Unknown status reply\n"); break; + case UP_REDIRECT: + fprintf(log, "FATAL: " + "Server send a redirection, configuration error?\n"); + ret = EXIT_FAILURE; + goto out; case UP_INTERNAL: fprintf(log, "FATAL: " "Internal error, likely run " @@ -861,9 +979,11 @@ cfgtoggle: ret = EXIT_FAILURE; goto out; case UP_NETWORK: + if (!running) + goto out; fprintf(log, "WARNING: " "Networking error\n"); - break; + goto retry; } next_refresh = now + minimum_refresh + @@ -872,18 +992,28 @@ cfgtoggle: fprintf(log, "Next forced update at %s", ctime(&next_refresh)); fflush(log); - } - tmp = ip; - ip = prev_ip; - prev_ip = tmp; - } else { + tmp = ip; + ip = prev_ip; + prev_ip = tmp; + } + } else if (running) { fprintf(log, "IP check failed.\n"); fflush(log); } - sleep(check_interval + - rand() * check_interval_rand / RAND_MAX); +retry: + sigprocmask(SIG_BLOCK, &sigset_all, NULL); + if (!running) + break; + + pselect(0, NULL, NULL, NULL, &(struct timespec){ + .tv_sec = (last_error + error_fallback < now ? + check_interval + rand() * check_interval_rand / RAND_MAX : + last_error + error_fallback - now + rand() * error_fallback_rand / RAND_MAX) + }, &sigset_dfl); + + sigprocmask(SIG_BLOCK, &sigset_hup, NULL); } out: