QUOTE: Be your own kind of beautiful.

feat: Initial commit - dns - Minimal dynamic DNS updater

dns

Minimal dynamic DNS updater
git clone git@soophie.de:/srv/git/dns
log | files | refs | readme

commit c82c80e62bfab015e1ad05c6dd6b981962fca604
Author: typable <contact@typable.dev>
Date:   Sat, 14 Dec 2024 22:34:07 +0100

feat: Initial commit

Diffstat:
AMakefile | 13+++++++++++++
AREADME.md | 1+
Adns.sh | 5+++++
Asrc/main.c | 200+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 219 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,13 @@ +CC=cc +CFLAGS=-Wall -Wextra -Werror -pedantic +LIBS=-lcurl + +build: src/*.c + $(CC) -o dns $(CFLAGS) $(LIBS) $^ + +install: build + cp dns /usr/local/bin/ + rm ./dns + +clean: + rm ./dns diff --git a/README.md b/README.md @@ -0,0 +1 @@ +# dns diff --git a/dns.sh b/dns.sh @@ -0,0 +1,5 @@ +export DNS_UPDATE_URL="https://dynamicdns.park-your-domain.com/update?host={HOST}&domain={DOMAIN}&password={PASSWD}&ip={IP_ADDR}" +export DNS_LOOKUP_URL="https://dns.google.com/resolve?name={HOST}.{DOMAIN}&type=A" +export DNS_RESOLVE_URL="http://checkip.amazonaws.com" + +./dns "<HOST>" "<DOMAIN>" "<PASSWD>" diff --git a/src/main.c b/src/main.c @@ -0,0 +1,200 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <curl/curl.h> +#define LIB_STR_IMPL +#include <libstr.h> + +#define DNS_ENV_UPDATE "DNS_UPDATE_URL" +#define DNS_ENV_LOOKUP "DNS_LOOKUP_URL" +#define DNS_ENV_RESOLVE "DNS_RESOLVE_URL" + +#define DNS_SYMBOL_HOST "HOST" +#define DNS_SYMBOL_DOMAIN "DOMAIN" +#define DNS_SYMBOL_IP_ADDR "IP_ADDR" +#define DNS_SYMBOL_PASSWD "PASSWD" + +#define DNS_DOMAIN_WILDCARD "@" + +typedef enum { + DNS_ERR_OK, + DNS_ERR_FAIL, +} dns_error_t; + +typedef struct { + char *host; + char *domain; + char *passwd; +} dns_domain_t; + +size_t dns_curl_write(void *ptr, size_t size, size_t nmemb, void *data) { + size_t len = size * nmemb; + char **curl_data = (char **) data; + str_append_len(curl_data, ptr, len); + return len; +} + +dns_error_t dns_update(char *url, dns_domain_t *domain, char *ip_addr) { + CURL *curl = curl_easy_init(); + if (!curl) { + fprintf(stderr, "CURL init failed!\n"); + return DNS_ERR_FAIL; + } + char *resolved_url = url; + resolved_url = str_replace_all(resolved_url, "{" DNS_SYMBOL_HOST "}", domain->host); + resolved_url = str_replace_all(resolved_url, "{" DNS_SYMBOL_DOMAIN "}", domain->domain); + resolved_url = str_replace_all(resolved_url, "{" DNS_SYMBOL_PASSWD "}", domain->passwd); + resolved_url = str_replace_all(resolved_url, "{" DNS_SYMBOL_IP_ADDR "}", ip_addr); + char *curl_data = NULL; + curl_easy_setopt(curl, CURLOPT_URL, resolved_url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dns_curl_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &curl_data); + CURLcode code = curl_easy_perform(curl); + free(resolved_url); + if (code != CURLE_OK) { + curl_easy_cleanup(curl); + fprintf(stderr, "CURL fetch failed!\n%s\n", curl_easy_strerror(code)); + return DNS_ERR_FAIL; + } + curl_easy_cleanup(curl); + for (size_t i = 0; i < strlen(curl_data); i++) { + if (strncmp(curl_data + i, "<ErrCount>0</ErrCount>", 22) == 0) { + free(curl_data); + return DNS_ERR_OK; + } + } + fprintf(stderr, "DNS update error:\n%s\n", curl_data); + if (curl_data != NULL) { + free(curl_data); + } + return DNS_ERR_FAIL; +} + +dns_error_t dns_lookup(char *url, dns_domain_t *domain, char **ip_addr) { + CURL *curl = curl_easy_init(); + if (!curl) { + fprintf(stderr, "CURL init failed!\n"); + return DNS_ERR_FAIL; + } + char *resolved_url = url; + resolved_url = str_replace_all(resolved_url, "{" DNS_SYMBOL_HOST "}", domain->host); + resolved_url = str_replace_all(resolved_url, "{" DNS_SYMBOL_DOMAIN "}", domain->domain); + char *curl_data = NULL; + curl_easy_setopt(curl, CURLOPT_URL, resolved_url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dns_curl_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &curl_data); + CURLcode code = curl_easy_perform(curl); + free(resolved_url); + if (code != CURLE_OK) { + curl_easy_cleanup(curl); + fprintf(stderr, "CURL fetch failed!\n%s\n", curl_easy_strerror(code)); + return DNS_ERR_FAIL; + } + curl_easy_cleanup(curl); + size_t set = 0; + for (size_t i = 0; i < strlen(curl_data);) { + if (strncmp(curl_data + i, "\"data\":\"", 8) == 0) { + i += 8; + set = i; + continue; + } + if (set > 0 && curl_data[i] == '"') { + str_append_len(ip_addr, curl_data + set, i - set); + break; + } + i++; + } + if (curl_data != NULL) { + free(curl_data); + } + return DNS_ERR_OK; +} + +dns_error_t dns_resolve(char *url, char **ip_addr) { + CURL *curl = curl_easy_init(); + if (!curl) { + fprintf(stderr, "CURL init failed!\n"); + return DNS_ERR_FAIL; + } + char *curl_data = NULL; + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dns_curl_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &curl_data); + CURLcode code = curl_easy_perform(curl); + if (code != CURLE_OK) { + curl_easy_cleanup(curl); + fprintf(stderr, "CURL fetch failed!\n%s\n", curl_easy_strerror(code)); + return DNS_ERR_FAIL; + } + curl_easy_cleanup(curl); + for (size_t i = 0; i < strlen(curl_data); i++) { + if (curl_data[i] == '\n') { + str_append_len(ip_addr, curl_data, i); + break; + } + } + if (curl_data != NULL) { + free(curl_data); + } + return DNS_ERR_OK; +} + +int main(int argc, char **argv) { + if (argc != 4) { + fprintf(stderr, "Invalid arguments!\nUsage: dns <host> <domain> <passwd>\n"); + return EXIT_FAILURE; + } + dns_domain_t domain = { + .host = argv[1], + .domain = argv[2], + .passwd = argv[3], + }; + if (strcmp(domain.host, DNS_DOMAIN_WILDCARD) == 0) { + fprintf(stdout, "DNS domain: %s\n", domain.domain); + } + else { + fprintf(stdout, "DNS domain: %s.%s\n", domain.host, domain.domain); + } + char *dns_update_url = getenv(DNS_ENV_UPDATE); + if (dns_update_url == NULL) { + fprintf(stderr, "Environment variable missing!\nDefine: " DNS_ENV_UPDATE "\n"); + return EXIT_FAILURE; + } + char *dns_lookup_url = getenv(DNS_ENV_LOOKUP); + if (dns_lookup_url == NULL) { + fprintf(stderr, "Environment variable missing!\nDefine: " DNS_ENV_LOOKUP "\n"); + return EXIT_FAILURE; + } + char *dns_resolve_url = getenv(DNS_ENV_RESOLVE); + if (dns_resolve_url == NULL) { + fprintf(stderr, "Environment variable missing!\nDefine: " DNS_ENV_RESOLVE "\n"); + return EXIT_FAILURE; + } + char *ip_addr = NULL; + if (dns_resolve(dns_resolve_url, &ip_addr) != DNS_ERR_OK) { + fprintf(stderr, "IP address resolution failed!\n"); + return EXIT_FAILURE; + } + fprintf(stdout, "DNS resolve IP_ADDR: %s\n", ip_addr); + char *domain_ip_addr = NULL; + if (dns_lookup(dns_lookup_url, &domain, &domain_ip_addr) != DNS_ERR_OK) { + fprintf(stderr, "DNS lookup failed!\n"); + return EXIT_FAILURE; + } + fprintf(stdout, "DNS lookup DOMAIN_IP_ADDR: %s\n", domain_ip_addr); + if (strcmp(ip_addr, domain_ip_addr) != 0) { + if (dns_update(dns_update_url, &domain, ip_addr) != DNS_ERR_OK) { + free(ip_addr); + free(domain_ip_addr); + fprintf(stderr, "DNS update FAILED!\n"); + return EXIT_FAILURE; + } + fprintf(stdout, "DNS update SUCCESSFUL.\n"); + } + else { + fprintf(stdout, "DNS domain already UP-TO-DATE.\n"); + } + free(ip_addr); + free(domain_ip_addr); + return EXIT_SUCCESS; +}