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:
M | Makefile | | | 10 | ++++++++-- |
A | config.def.h | | | 18 | ++++++++++++++++++ |
M | udyfi.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: