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:
M | Makefile | | | 14 | ++++++++++---- |
A | arg.h | | | 30 | ++++++++++++++++++++++++++++++ |
A | minitls.h | | | 121 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | udyfi.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;
}