udyfi

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

commit 80a965952f3fcb512da472186846766b66ddba6e
parent ed22dbecf30e03a528eb0b4993e23d3960323229
Author: Santtu Lakkala <inz@inz.fi>
Date:   Fri, 16 Jun 2023 15:19:45 +0300

Refactor and add libtls support

 - Use libtls API, with a thin wrapper around OpenSSL
 - Drop getopt(), use modified version of arg.h instead
 - Support reading password from file
 - Clean exit on signals

Diffstat:
MMakefile | 14++++++++++----
Aarg.h | 30++++++++++++++++++++++++++++++
Aminitls.h | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mudyfi.c | 337+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
4 files changed, 354 insertions(+), 148 deletions(-)

diff --git a/Makefile b/Makefile @@ -3,12 +3,18 @@ CFLAGS += -W -Wall -std=c99 -pedantic -D_POSIX_C_SOURCE=200809L PREFIX ?= /usr/local BINDIR := $(PREFIX)/bin IFADDRS_CFLAGS ?= -DUSE_IFADDRS -OPENSSL_CFLAGS += $(shell pkg-config openssl --cflags && echo -DUSE_OPENSSL) -LIBS := $(shell pkg-config openssl --libs) +HEADERS = arg.h + +LIBTLS_CFLAGS += -DUSE_LIBTLS -I/usr/local/include +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 -udyfi: udyfi.c - $(CROSS)$(CC) $(CFLAGS) $(OPENSSL_CFLAGS) $(IFADDRS_CFLAGS) $< -o $@ $(LIBS) +udyfi: udyfi.c $(HEADERS) + $(CROSS)$(CC) $(CFLAGS) $(LIBTLS_CFLAGS) $(OPENSSL_CFLAGS) $(IFADDRS_CFLAGS) $< -o $@ $(LIBS) clean: rm -f udyfi diff --git a/arg.h b/arg.h @@ -0,0 +1,30 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H +#define ARG_H + +extern char *argv0; + +#define ARGBEGIN for(argv0 = *argv, argv++, argc--;\ + argc && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char *_arg;\ + if(argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for(_arg = argv[0] + 1; *_arg;) {\ + switch (*_arg++) + +#define ARGEND }\ + } + +#define EARGF(x) (argc ? (argc--, argv++, argv[0]) : \ + ((x), abort(), (char *)0)) +#endif + diff --git a/minitls.h b/minitls.h @@ -0,0 +1,121 @@ +/* + * MIT License + * + * Copyright (c) 2023 Santtu Lakkala + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#ifndef MINITLS_H +#define MINITLS_H +#include <openssl/ssl.h> +#include <stdbool.h> + +#define TLS_WANT_POLLIN -2 +#define TLS_WANT_POLLOUT -3 + +struct tls { + SSL_CTX *ctx; + SSL *ssl; + int err; +}; + +static struct tls *tls_client(void) +{ + static bool ready = false; + const SSL_METHOD *method; + struct tls *rv = malloc(sizeof(*rv)); + + if (!rv) + return NULL; + + if (!ready) { + SSL_library_init(); + OpenSSL_add_all_algorithms(); + ready = true; + } + + method = TLS_client_method(); + rv->ctx = SSL_CTX_new(method); + rv->ssl = NULL; + SSL_CTX_set_default_verify_paths(rv->ctx); + + return rv; +} + +static ssize_t tls_read(struct tls *ctx, void *buf, size_t buflen) +{ + ssize_t r = SSL_read(ctx->ssl, buf, buflen); + if (r >= 0) + return r; + ctx->err = r; + r = SSL_get_error(ctx->ssl, r); + if (r == SSL_ERROR_WANT_READ) + return TLS_WANT_POLLIN; + if (r == SSL_ERROR_WANT_WRITE) + return TLS_WANT_POLLOUT; + return -1; +} + +static ssize_t tls_write(struct tls *ctx, const void *buf, size_t buflen) +{ + ssize_t r = SSL_write(ctx->ssl, buf, buflen); + if (r >= 0) + return r; + ctx->err = r; + r = SSL_get_error(ctx->ssl, r); + if (r == SSL_ERROR_WANT_READ) + return TLS_WANT_POLLIN; + if (r == SSL_ERROR_WANT_WRITE) + return TLS_WANT_POLLOUT; + return -1; +} + +static void tls_reset(struct tls *ctx) +{ + SSL_free(ctx->ssl); + ctx->ssl = NULL; +} + +static int tls_connect_socket(struct tls *ctx, int fd, const char *servername) +{ + if (!(ctx->ssl = SSL_new(ctx->ctx))) + return -1; + + SSL_set_fd(ctx->ssl, fd); + SSL_set_tlsext_host_name(ctx->ssl, servername); + if ((ctx->err = SSL_connect(ctx->ssl)) != 1) + return -1; + return 0; +} + +static int tls_close(struct tls *ctx) +{ + SSL_shutdown(ctx->ssl); + return 0; +} + +static void tls_free(struct tls *ctx) +{ + SSL_CTX_free(ctx->ctx); + free(ctx); +} + +#define tls_configure(x, y) + +#endif diff --git a/udyfi.c b/udyfi.c @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2016-2020 Santtu Lakkala + * Copyright (c) 2016-2023 Santtu Lakkala * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), @@ -21,7 +21,12 @@ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ +#include <sys/cdefs.h> +#undef __BSD_VISIBLE +#define __BSD_VISIBLE 1 #include <sys/socket.h> +#include <signal.h> +#include <poll.h> #include <fcntl.h> #include <unistd.h> #include <netdb.h> @@ -32,14 +37,19 @@ #include <stdlib.h> #include <stdbool.h> #ifdef USE_IFADDRS -#include <ifaddrs.h> #define __USE_MISC #include <net/if.h> +#include <ifaddrs.h> #endif #include <time.h> -#ifdef USE_OPENSSL -#include <openssl/ssl.h> +#if defined(USE_LIBTLS) +#include <tls.h> +#elif defined(USE_OPENSSL) +#undef USE_OPENSSL +#define USE_LIBTLS +#include "minitls.h" #endif +#include "arg.h" static const time_t minimum_refresh = 5 * 24 * 60 * 60; static const time_t minimum_refresh_rand = 10 * 60 * 60; @@ -53,34 +63,44 @@ 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; + +#ifdef USE_LIBTLS +#define IF_SSL(...) __VA_ARGS__ +#else +#define IF_SSL(...) +#endif + +static void sig_handler(int signum) +{ + (void)signum; + running = false; +} + static ssize_t http_get(const char *server, const char *port, const char *path, const char *headers, const char *iface, - int af, + int *af, char *buffer, size_t bufsize -#ifdef USE_OPENSSL - , SSL_CTX *ctx -#endif + IF_SSL(, struct tls *ctx) ) { struct addrinfo *res; struct addrinfo *i; struct addrinfo *k; - struct addrinfo hints = { .ai_family = af }; + struct addrinfo hints = { .ai_family = *af, .ai_socktype = SOCK_STREAM }; struct addrinfo *local = NULL; #ifdef USE_IFADDRS struct ifaddrs *addrs = NULL; struct ifaddrs *j; #endif - int r; - int s; + ssize_t r; + ssize_t s; int sock; -#ifdef USE_OPENSSL - SSL *ssl = NULL; -#endif if (iface) { #if USE_IFADDRS @@ -160,6 +180,7 @@ static ssize_t http_get(const char *server, } } + *af = i->ai_family; if (!connect(sock, i->ai_addr, i->ai_addrlen)) break; close(sock); @@ -183,22 +204,17 @@ static ssize_t http_get(const char *server, return -1; } -#ifdef USE_OPENSSL +#ifdef USE_LIBTLS if (ctx) { - ssl = SSL_new(ctx); - - if (!ssl) { + if (tls_connect_socket(ctx, sock, server)) { close(sock); return -1; } - SSL_set_fd(ssl, sock); - SSL_set_tlsext_host_name(ssl, server); - SSL_connect(ssl); - } - if (ssl) { - if ((s = SSL_write(ssl, buffer, r)) < r) { - SSL_free(ssl); + if ((s = tls_write(ctx, buffer, r)) < r) { + tls_close(ctx); + tls_reset(ctx); + tls_configure(ctx, NULL); close(sock); return -1; } @@ -211,25 +227,36 @@ static ssize_t http_get(const char *server, s = 0; do { -#ifdef USE_OPENSSL - if (ssl) - r = SSL_read(ssl, buffer + s, bufsize - s - 1); +#ifdef USE_LIBTLS + if (ctx) + r = tls_read(ctx, buffer + s, bufsize - s - 1); else #endif r = read(sock, buffer + s, bufsize - s - 1); if (r < 0) { -#ifdef USE_OPENSSL - if (ssl) - SSL_free(ssl); +#ifdef USE_LIBTLS + if (ctx) { + if (r == TLS_WANT_POLLIN) { + poll(&(struct pollfd){ .fd = sock, .events = POLLIN }, 1, 0); + continue; + } + + tls_close(ctx); + tls_reset(ctx); + tls_configure(ctx, NULL); + } #endif close(sock); return -1; } s += r; } while (r && s < (int)bufsize); -#ifdef USE_OPENSSL - if (ssl) - SSL_free(ssl); +#ifdef USE_LIBTLS + if (ctx) { + tls_close(ctx); + tls_reset(ctx); + tls_configure(ctx, NULL); + } #endif close(sock); buffer[s] = '\0'; @@ -243,9 +270,7 @@ static int check_ip(const char *server, int af, char *ip_buffer, size_t ip_size -#ifdef USE_OPENSSL - , SSL_CTX *ctx -#endif + IF_SSL(, struct tls *ctx) ) { char buffer[1024]; @@ -255,21 +280,27 @@ static int check_ip(const char *server, if (http_get(server, port, "/", NULL, iface, - af, + &af, buffer, sizeof(buffer) -#ifdef USE_OPENSSL - , ctx -#endif + IF_SSL(, ctx) ) < 0) return -1; ip = strstr(buffer, "Current IP Address: "); if (!ip) return -1; - ip += sizeof("Current IP Address: ") - 1; - iplen = strspn(ip, "0123456789."); + ip += sizeof("Current IP Address: ") - 1; + switch (af) { + case AF_INET: + default: + iplen = strspn(ip, "0123456789."); + break; + case AF_INET6: + iplen = strspn(ip, "0123456789a-fA-F:"); + break; + } if (iplen > ip_size) return -1; sprintf(ip_buffer, "%.*s", (int)iplen, ip); @@ -373,9 +404,7 @@ static int update_ip(const char *server, const char *password, const char *iface, int af -#ifdef USE_OPENSSL - , SSL_CTX *ctx -#endif + IF_SSL(, struct tls *ctx) ) { char buffer[1024]; @@ -405,11 +434,9 @@ static int update_ip(const char *server, return UP_INTERNAL; if ((r = http_get(server, port, path_buffer, - auth_buffer, iface, af, + auth_buffer, iface, &af, buffer, sizeof(buffer) -#ifdef USE_OPENSSL - , ctx -#endif + IF_SSL(, ctx) )) < 0) return UP_NETWORK; @@ -427,11 +454,12 @@ static int update_ip(const char *server, return r; } -static void usage(const char *argv0) { +static void usage(void) { fprintf(stderr, "Usage: %s " "-u <username> " "-p <password> " + "-f <passwordfile> " "[-i <" #ifdef USE_IFADDRS "interface/" @@ -443,10 +471,10 @@ static void usage(const char *argv0) { "[-D <dyndns port>] " "[-b] " "[-P <pidfile>] " -#ifdef USE_OPENSSL + IF_SSL( "[-s] " "[-S] " -#endif + ) "[-6] " "[-4] " "host[,host...]\n", @@ -483,7 +511,7 @@ int main(int argc, char **argv) const char *pidfile = NULL; const char *iface = NULL; char password[256] = ""; - int i; + int fd; time_t next_refresh = 0; char ipbuf1[64]; char ipbuf2[64] = ""; @@ -491,104 +519,109 @@ int main(int argc, char **argv) char *prev_ip = ipbuf2; int background = 0; int af = AF_UNSPEC; + int ret = EXIT_SUCCESS; FILE *log = stderr; const char *logfile = NULL; -#ifdef USE_OPENSSL +#ifdef USE_LIBTLS int port_set = 0; int check_port_set = 0; - int ssl = 0; + int use_ssl = 0; int check_ssl = 0; - const SSL_METHOD *method; - SSL_CTX *ctx = NULL; + struct tls *ctx = NULL; #endif - while ((i = getopt(argc, argv, "u:p:c:C:d:D:bP:l:i:46" -#ifdef USE_OPENSSL - "sS" -#endif - )) != -1) { - switch (i) { - case 'i': - iface = optarg; - break; - case 'u': - username = optarg; - break; - case 'p': - snprintf(password, sizeof(password), - "%s", optarg); - memset(optarg, 0, strlen(optarg)); - break; - case 'c': - checkip_service = optarg; - break; - case '4': - af = AF_INET; - break; - case '6': - af = AF_INET6; - break; - case 'C': - checkip_port = optarg; -#ifdef USE_OPENSSL - check_port_set = 1; -#endif - break; - case 'd': - dyndns_service = optarg; - break; - case 'D': - dyndns_port = optarg; -#ifdef USE_OPENSSL - port_set = 1; -#endif - break; - case 'b': - background = 1; - break; - case 'P': - pidfile = optarg; - break; - case 'l': - logfile = optarg; - break; -#ifdef USE_OPENSSL - case 's': - ssl = 1; - break; - case 'S': - check_ssl = 1; - break; -#endif - default: - usage(argv[0]); + 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; + } + + 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; +#endif + default: + return EXIT_FAILURE; + } ARGEND -#ifdef USE_OPENSSL - if (ssl || check_ssl) { - SSL_library_init(); - OpenSSL_add_all_algorithms(); - method = SSLv23_client_method(); - ctx = SSL_CTX_new(method); - SSL_CTX_set_default_verify_paths(ctx); +#if defined(USE_LIBTLS) + if (use_ssl || check_ssl) { + ctx = tls_client(); if (!ctx) { fprintf(stderr, "SSL/TLS initialization failed.\n"); return EXIT_FAILURE; } - if (ssl && !port_set) + if (use_ssl && !port_set) dyndns_port = "443"; if (check_ssl && !check_port_set) checkip_port = "443"; } #endif - if (optind >= argc || !username) { - usage(argv[0]); + if (argc < 1 || !username) { + usage(); return EXIT_FAILURE; } @@ -604,37 +637,39 @@ int main(int argc, char **argv) fprintf(log, "udyfi started\n"); - for (;;) { + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + while (running) { time_t now = time(NULL); if (check_ip(checkip_service, checkip_port, iface, af, ip, sizeof(ipbuf1) -#ifdef USE_OPENSSL - , check_ssl ? ctx : NULL -#endif + IF_SSL(, check_ssl ? ctx : NULL) ) >= 0) { char *tmp; if ((strcmp(ip, prev_ip) || now > next_refresh)) { switch (update_ip(dyndns_service, dyndns_port, - argv[optind], + *argv, username, password, iface, af -#ifdef USE_OPENSSL - , ssl ? ctx : NULL -#endif + IF_SSL(, use_ssl ? ctx : NULL) )) { case UP_BADAUTH: fprintf(log, "FATAL: " "Authentication error\n"); - return EXIT_FAILURE; + ret = EXIT_FAILURE; + goto out; case UP_NOHOST: fprintf(log, "FATAL: " "No hostnames specified\n"); - return EXIT_FAILURE; + ret = EXIT_FAILURE; + goto out; case UP_NOTFQDN: fprintf(log, "FATAL: " "Malformed hostnames\n"); - return EXIT_FAILURE; + ret = EXIT_FAILURE; + goto out; case UP_BADIP: fprintf(log, "WARNING: " "Server rejected our IP\n"); @@ -645,7 +680,7 @@ int main(int argc, char **argv) break; case UP_GOOD: fprintf(log, "%s now point to %s\n", - argv[optind], ip); + *argv, ip); break; case UP_DNSERR: fprintf(log, "WARNING: " @@ -654,7 +689,8 @@ int main(int argc, char **argv) case UP_ABUSE: fprintf(log, "FATAL: " "We are flagged as abuse\n"); - return EXIT_FAILURE; + ret = EXIT_FAILURE; + goto out; case UP_UNKNOWN: fprintf(log, "WARNING: " "Unknown status reply\n"); @@ -663,7 +699,8 @@ int main(int argc, char **argv) fprintf(log, "FATAL: " "Internal error, likely run " "out of buffer\n"); - return EXIT_FAILURE; + ret = EXIT_FAILURE; + goto out; case UP_NETWORK: fprintf(log, "WARNING: " "Networking error\n"); @@ -690,5 +727,17 @@ int main(int argc, char **argv) rand() * check_interval_rand / RAND_MAX); } - return 0; +out: +#ifdef USE_LIBTLS + if (ctx) + tls_free(ctx); +#endif + + if (pidfile) + unlink(pidfile); + + if (log != stderr) + fclose(log); + + return ret; }