commit 96d411cb6794581dc06b32f05bf8dbc873390e2a
Author: Santtu Lakkala <inz@inz.fi>
Date: Wed, 8 Apr 2020 10:31:15 +0300
Initial import
Diffstat:
A | Makefile | | | 18 | ++++++++++++++++++ |
A | udyfi.c | | | 695 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 713 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,18 @@
+CFLAGS ?= -Os
+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)
+all: udyfi
+
+udyfi: udyfi.c
+ $(CROSS)$(CC) $(CFLAGS) $(OPENSSL_CFLAGS) $(IFADDRS_CFLAGS) $< -o $@ $(LIBS)
+
+clean:
+ rm -f udyfi
+
+install: udyfi
+ install -d $(DESTDIR)$(BINDIR)
+ install udyfi $(DESTDIR)$(BINDIR)
diff --git a/udyfi.c b/udyfi.c
@@ -0,0 +1,695 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2016-2020 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.
+ */
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#ifdef USE_IFADDRS
+#include <ifaddrs.h>
+#define __USE_MISC
+#include <net/if.h>
+#endif
+#include <time.h>
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>
+#endif
+
+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 ssize_t http_get(const char *server,
+ const char *port,
+ const char *path,
+ const char *headers,
+ const char *iface,
+ int af,
+ char *buffer,
+ size_t bufsize
+#ifdef USE_OPENSSL
+ , SSL_CTX *ctx
+#endif
+ )
+{
+ struct addrinfo *res;
+ struct addrinfo *i;
+ struct addrinfo *k;
+ struct addrinfo hints = { .ai_family = af };
+ struct addrinfo *local = NULL;
+#ifdef USE_IFADDRS
+ struct ifaddrs *addrs = NULL;
+ struct ifaddrs *j;
+#endif
+ int r;
+ int s;
+ int sock;
+#ifdef USE_OPENSSL
+ SSL *ssl = NULL;
+#endif
+
+ if (iface) {
+#if USE_IFADDRS
+ bool if_found = false;
+ if ((r = getifaddrs(&addrs)))
+ return -1;
+ for (j = addrs; j; j = j->ifa_next) {
+ if (!strcmp(j->ifa_name, iface)) {
+ if_found = true;
+ if (((j->ifa_flags & (IFF_UP |
+ IFF_BROADCAST |
+ IFF_LOOPBACK)) ==
+ (IFF_UP | IFF_BROADCAST)))
+ break;
+ }
+ }
+ if (if_found && !j) {
+ freeifaddrs(addrs);
+ return -1;
+ }
+
+ if (!if_found) {
+ freeifaddrs(addrs);
+ addrs = NULL;
+
+#endif
+ r = getaddrinfo(iface, NULL, &hints, &local);
+ if (r < 0)
+ return -1;
+#if USE_IFADDRS
+ }
+#endif
+ }
+
+ if ((r = getaddrinfo(server, port, &hints, &res))) {
+#if USE_IFADDRS
+ if (addrs)
+ freeifaddrs(addrs);
+#endif
+ if (local)
+ freeaddrinfo(local);
+ return -1;
+ }
+
+ for (i = res; i; i = i->ai_next) {
+ sock = socket(i->ai_family, i->ai_socktype, i->ai_protocol);
+
+#if USE_IFADDRS
+ if (addrs) {
+ for (j = addrs; j; j = j->ifa_next) {
+ if (!strcmp(j->ifa_name, iface) &&
+ (((j->ifa_flags & (IFF_UP |
+ IFF_BROADCAST |
+ IFF_LOOPBACK)) ==
+ (IFF_UP | IFF_BROADCAST))) &&
+ j->ifa_addr->sa_family == i->ai_family)
+ break;
+ }
+ if (j) {
+ if ((r = bind(sock,
+ j->ifa_addr, sizeof(struct sockaddr_storage)))) {
+ close(sock);
+ continue;
+ }
+ }
+ } else
+#endif
+ if (local) {
+ for (k = local; k; k = k->ai_next) {
+ if (k->ai_family == i->ai_family &&
+ !bind(sock, k->ai_addr, k->ai_addrlen))
+ break;
+ }
+ if (!k) {
+ close(sock);
+ continue;
+ }
+ }
+
+ if (!connect(sock, i->ai_addr, i->ai_addrlen))
+ break;
+ close(sock);
+ }
+
+ freeaddrinfo(res);
+#if USE_IFADDRS
+ if (addrs)
+ freeifaddrs(addrs);
+#endif
+ if (local)
+ freeaddrinfo(local);
+
+ if (!i)
+ return -1;
+
+ if ((r = snprintf(buffer, bufsize,
+ "GET %s HTTP/1.0\r\nHost: %s\r\n%s\n",
+ path, server, headers ? headers : "")) >= (int)bufsize) {
+ close(sock);
+ return -1;
+ }
+
+#ifdef USE_OPENSSL
+ if (ctx) {
+ ssl = SSL_new(ctx);
+
+ if (!ssl) {
+ 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);
+ close(sock);
+ return -1;
+ }
+ } else
+#endif
+ if ((s = write(sock, buffer, r)) < r) {
+ close(sock);
+ return -1;
+ }
+
+ s = 0;
+ do {
+#ifdef USE_OPENSSL
+ if (ssl)
+ r = SSL_read(ssl, 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);
+#endif
+ close(sock);
+ return -1;
+ }
+ s += r;
+ } while (r && s < (int)bufsize);
+#ifdef USE_OPENSSL
+ if (ssl)
+ SSL_free(ssl);
+#endif
+ close(sock);
+ buffer[s] = '\0';
+
+ return s;
+}
+
+static int check_ip(const char *server,
+ const char *port,
+ const char *iface,
+ int af,
+ char *ip_buffer,
+ size_t ip_size
+#ifdef USE_OPENSSL
+ , SSL_CTX *ctx
+#endif
+ )
+{
+ char buffer[1024];
+ const char *ip;
+ size_t iplen;
+
+ if (http_get(server, port, "/",
+ NULL,
+ iface,
+ af,
+ buffer,
+ sizeof(buffer)
+#ifdef USE_OPENSSL
+ , ctx
+#endif
+ ) < 0)
+ return -1;
+
+ printf("%s\n", buffer);
+ ip = strstr(buffer, "Current IP Address: ");
+ if (!ip)
+ return -1;
+ ip += sizeof("Current IP Address: ") - 1;
+
+ iplen = strspn(ip, "0123456789.");
+ if (iplen > ip_size)
+ return -1;
+ sprintf(ip_buffer, "%.*s", (int)iplen, ip);
+
+ return 0;
+}
+
+static int auth_token(const char *user,
+ const char *password,
+ char *buffer,
+ size_t b_len)
+{
+ static const char *base64 =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+ size_t ulen = strlen(user);
+ size_t plen = strlen(password);
+ size_t i;
+ size_t bits = 0;
+ uint32_t d = 0;
+ char *wp = buffer;
+
+ if ((ulen + plen + 3) / 3 * 4 >= b_len)
+ return -1;
+
+ for (i = 0; i < ulen; i++) {
+ d = (d << 8) | user[i];
+ bits += 8;
+ while (bits >= 6)
+ *wp++ = base64[(d >> (bits -= 6)) & 0x3f];
+ }
+ d = (d << 8) | ':';
+ bits += 8;
+ for (i = 0; i < plen; i++) {
+ d = (d << 8) | password[i];
+ bits += 8;
+ while (bits >= 6)
+ *wp++ = base64[(d >> (bits -= 6)) & 0x3f];
+ }
+
+ if (bits)
+ *wp++ = base64[(d << (6 - bits)) & 0x3f];
+ while ((wp - buffer) & 3)
+ *wp++ = '=';
+ *wp = '\0';
+
+ return wp - buffer;
+}
+
+static const char *find_body(const char *response,
+ size_t len, size_t *blen)
+{
+ size_t i;
+
+ for (i = 0; i < len - 1; i++) {
+ if (response[i] == '\n') {
+ if (response[i + 1] == '\n') {
+ *blen = len - i - 2;
+ return response + i + 2;
+ } else if (i < len - 2 && response[i + 1] == '\r' &&
+ response[i + 2] == '\n') {
+ *blen = len - i - 3;
+ return response + i + 3;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+enum update_status {
+ UP_BADAUTH,
+ UP_NOHOST,
+ UP_NOTFQDN,
+ UP_BADIP,
+ UP_NOCHG,
+ UP_GOOD,
+ UP_DNSERR,
+ UP_ABUSE,
+ UP_UNKNOWN,
+ UP_INTERNAL,
+ UP_NETWORK
+};
+
+static const char *statusstr[] = {
+ "badauth",
+ "nohost",
+ "notfqdn",
+ "badip ",
+ "nochg",
+ "good ",
+ "dnserr",
+ "abuse"
+};
+
+static int update_ip(const char *server,
+ const char *port,
+ const char *hosts,
+ const char *username,
+ const char *password,
+ const char *iface,
+ int af
+#ifdef USE_OPENSSL
+ , SSL_CTX *ctx
+#endif
+ )
+{
+ char buffer[1024];
+ char auth_buffer[1024];
+ char path_buffer[1024];
+ int pos;
+ int r;
+ size_t bl;
+ const char *body;
+
+ 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))
+ return UP_INTERNAL;
+
+ pos = snprintf(path_buffer, sizeof(path_buffer),
+ "/nic/update?hostname=%s", hosts);
+ if (pos >= (int)sizeof(path_buffer))
+ return UP_INTERNAL;
+
+ if ((r = http_get(server, port, path_buffer,
+ auth_buffer, iface, af,
+ buffer, sizeof(buffer)
+#ifdef USE_OPENSSL
+ , ctx
+#endif
+ )) < 0)
+ return UP_NETWORK;
+
+ if ((body = find_body(buffer, r,
+ &bl))) {
+ for (r = 0; r < UP_UNKNOWN; r++) {
+ if (!strncmp(body, statusstr[r],
+ strlen(statusstr[r])))
+ break;
+ }
+ } else {
+ r = UP_UNKNOWN;
+ }
+
+ return r;
+}
+
+static void usage(const char *argv0) {
+ fprintf(stderr,
+ "Usage: %s "
+ "-u <username> "
+ "-p <password> "
+ "[-i <"
+#ifdef USE_IFADDRS
+ "interface/"
+#endif
+ "local address>] "
+ "[-c <checkip server>] "
+ "[-C <checkip port>] "
+ "[-d <dyndns server>] "
+ "[-D <dyndns port>] "
+ "[-b] "
+ "[-P <pidfile>] "
+#ifdef USE_OPENSSL
+ "[-s] "
+ "[-S] "
+#endif
+ "[-6] "
+ "[-4] "
+ "host[,host...]\n",
+ argv0);
+}
+
+static void write_pidfile(const char *pidfile, pid_t pid)
+{
+ FILE *pdf = fopen(pidfile, "w");
+ if (pdf) {
+ fprintf(pdf, "%ld\n", (long)pid);
+ fclose(pdf);
+ }
+}
+
+static void daemonize(const char *pidfile) {
+ pid_t pid;
+ if ((pid = fork())) {
+ if (pid < 0)
+ exit(EXIT_FAILURE);
+ if (pidfile) {
+ write_pidfile(pidfile, pid);
+ }
+ exit(EXIT_SUCCESS);
+ }
+ setsid();
+ fclose(stdin);
+ fclose(stdout);
+}
+
+int main(int argc, char **argv)
+{
+ const char *username = NULL;
+ const char *pidfile = NULL;
+ const char *iface = NULL;
+ char password[256] = "";
+ int i;
+ time_t next_refresh = 0;
+ char ipbuf1[64];
+ char ipbuf2[64] = "";
+ char *ip = ipbuf1;
+ char *prev_ip = ipbuf2;
+ int background = 0;
+ int af = AF_UNSPEC;
+ FILE *log = stderr;
+ const char *logfile = NULL;
+
+#ifdef USE_OPENSSL
+ int port_set = 0;
+ int check_port_set = 0;
+ int ssl = 0;
+ int check_ssl = 0;
+ const SSL_METHOD *method;
+ SSL_CTX *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]);
+ return EXIT_FAILURE;
+ }
+ }
+
+#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 (!ctx) {
+ fprintf(stderr, "SSL/TLS initialization failed.\n");
+ return EXIT_FAILURE;
+ }
+
+ if (ssl && !port_set)
+ dyndns_port = "443";
+ if (check_ssl && !check_port_set)
+ checkip_port = "443";
+ }
+#endif
+
+ if (optind >= argc || !username) {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ srand(time(NULL));
+
+ if (logfile)
+ log = fopen(logfile, "a");
+
+ if (background)
+ daemonize(pidfile);
+ else if (pidfile)
+ write_pidfile(pidfile, getpid());
+
+ fprintf(log, "udyfi started\n");
+
+ for (;;) {
+ 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
+ ) >= 0) {
+ char *tmp;
+
+ if ((strcmp(ip, prev_ip) || now > next_refresh)) {
+ switch (update_ip(dyndns_service, dyndns_port,
+ argv[optind],
+ username, password, iface, af
+#ifdef USE_OPENSSL
+ , ssl ? ctx : NULL
+#endif
+ )) {
+ case UP_BADAUTH:
+ fprintf(log, "FATAL: "
+ "Authentication error\n");
+ return EXIT_FAILURE;
+ case UP_NOHOST:
+ fprintf(log, "FATAL: "
+ "No hostnames specified\n");
+ return EXIT_FAILURE;
+ case UP_NOTFQDN:
+ fprintf(log, "FATAL: "
+ "Malformed hostnames\n");
+ return EXIT_FAILURE;
+ case UP_BADIP:
+ fprintf(log, "WARNING: "
+ "Server rejected our IP\n");
+ break;
+ case UP_NOCHG:
+ fprintf(log, "IP %s refreshed\n",
+ ip);
+ break;
+ case UP_GOOD:
+ fprintf(log, "%s now point to %s\n",
+ argv[optind], ip);
+ break;
+ case UP_DNSERR:
+ fprintf(log, "WARNING: "
+ "Temporary service error\n");
+ break;
+ case UP_ABUSE:
+ fprintf(log, "FATAL: "
+ "We are flagged as abuse\n");
+ return EXIT_FAILURE;
+ case UP_UNKNOWN:
+ fprintf(log, "WARNING: "
+ "Unknown status reply\n");
+ break;
+ case UP_INTERNAL:
+ fprintf(log, "FATAL: "
+ "Internal error, likely run "
+ "out of buffer\n");
+ return EXIT_FAILURE;
+ case UP_NETWORK:
+ fprintf(log, "WARNING: "
+ "Networking error\n");
+ break;
+ }
+
+ next_refresh = now + minimum_refresh +
+ rand() * minimum_refresh_rand /
+ RAND_MAX;
+ fprintf(log, "Next forced update at %s",
+ ctime(&next_refresh));
+ fflush(log);
+ }
+
+ tmp = ip;
+ ip = prev_ip;
+ prev_ip = tmp;
+ } else {
+ fprintf(log, "IP check failed.\n");
+ fflush(log);
+ }
+
+ sleep(check_interval +
+ rand() * check_interval_rand / RAND_MAX);
+ }
+
+ return 0;
+}