summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordmlunar <root@lunar.sh>2026-01-23 15:21:03 +0200
committerdmlunar <root@lunar.sh>2026-01-23 15:21:03 +0200
commit506c89c969d932ae75482f89d28b1a33c27fc9e2 (patch)
tree4052c3b854e06653836904bdb80be3716428b551
downloadopenresolve-506c89c969d932ae75482f89d28b1a33c27fc9e2.tar.gz
openresolve-506c89c969d932ae75482f89d28b1a33c27fc9e2.zip
openalias: initial commit
Initial commit.
-rw-r--r--openalias.c242
-rw-r--r--premake5.lua30
2 files changed, 272 insertions, 0 deletions
diff --git a/openalias.c b/openalias.c
new file mode 100644
index 0000000..4ed1c40
--- /dev/null
+++ b/openalias.c
@@ -0,0 +1,242 @@
+#define _CRT_SECURE_NO_WARNINGS
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#include <windns.h>
+#pragma comment(lib, "dnsapi.lib")
+#else
+#include <resolv.h>
+#include <arpa/nameser.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <strings.h>
+#endif
+
+#define MAX_RECORDS 16
+#define STR_MAX 256
+#define STR_TICKER_MAX 8
+#define FQDN_MAX 253
+#define LABEL_MAX 63
+#define DNS_BUF_SIZE 8192
+#define VERSION "1.0"
+
+typedef struct {
+ char currency[STR_TICKER_MAX];
+ char address[STR_MAX];
+} openalias_record;
+
+#ifdef _WIN32
+#define str_icmp _stricmp
+#define str_nicmp _strnicmp
+#else
+#define str_icmp strcasecmp
+#define str_nicmp strncasecmp
+#endif
+
+static void normalize_input(char* s) {
+ for (; *s; ++s)
+ if (*s == '@')
+ *s = '.';
+}
+
+static void normalize_ticker(char* s) {
+ for (; *s; ++s)
+ *s = (char)toupper((unsigned char)*s);
+}
+
+static int is_valid_label(const char* s, size_t len) {
+ if (len == 0 || len > LABEL_MAX) return 0;
+ if (s[0] == '-' || s[len - 1] == '-') return 0;
+ for (size_t i = 0; i < len; i++) {
+ if (!(isalnum((unsigned char)s[i]) || s[i] == '-'))
+ return 0;
+ }
+ return 1;
+}
+
+static int is_valid_fqdn(const char* s) {
+ size_t len = strlen(s);
+ if (len < 3 || len > FQDN_MAX) return 0;
+
+ const char* label = s;
+ const char* p = s;
+ while (1) {
+ if (*p == '.' || *p == '\0') {
+ if (!is_valid_label(label, (size_t)(p - label)))
+ return 0;
+ if (*p == '\0') break;
+ label = p + 1;
+ }
+ p++;
+ }
+ return 1;
+}
+
+static int starts_with(const char* s, const char* prefix) {
+ return str_nicmp(s, prefix, strlen(prefix)) == 0;
+}
+
+
+static int parse_openalias(const char* txt,
+ openalias_record* rec,
+ const char* filter) {
+
+ if (!txt || !rec || !starts_with(txt, "oa1:")) return 0;
+
+ const char* p = txt + 4;
+ char currency[STR_TICKER_MAX] = { 0 };
+ size_t i = 0;
+
+ while (*p && !isspace((unsigned char)*p) && *p != ';' && i < STR_TICKER_MAX - 1)
+ currency[i++] = (char)toupper((unsigned char)*p++);
+ currency[i] = '\0';
+
+ if (currency[0] == '\0') return 0;
+ if (filter && str_icmp(filter, currency) != 0) return 0;
+
+ const char* addr = strstr(txt, "recipient_address=");
+ if (!addr) return 0;
+
+ addr += strlen("recipient_address=");
+ i = 0;
+ while (*addr && *addr != ';' && !isspace((unsigned char)*addr) && i < STR_MAX - 1)
+ rec->address[i++] = *addr++;
+ rec->address[i] = '\0';
+
+ if (rec->address[0] == '\0') return 0;
+
+ strncpy(rec->currency, currency, STR_TICKER_MAX - 1);
+ rec->currency[STR_TICKER_MAX - 1] = '\0';
+ return 1;
+}
+
+int main(int argc, char** argv) {
+
+ if (argc < 2) {
+ fprintf(stderr, "openalias address lookup v%s\n", VERSION);
+ fprintf(stderr, "usage: %s <domain> [-t ticker] (default=xmr)\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (strlen(argv[1]) > FQDN_MAX) {
+ fprintf(stderr, "error: domain name too long\n");
+ return EXIT_FAILURE;
+ }
+
+ char fqdn[FQDN_MAX + 1] = { 0 };
+ strncpy(fqdn, argv[1], FQDN_MAX);
+ fqdn[FQDN_MAX] = '\0';
+ normalize_input(fqdn);
+
+ if (!is_valid_fqdn(fqdn)) {
+ fprintf(stderr, "error: invalid fqdn\n");
+ return EXIT_FAILURE;
+ }
+
+ char filter_buf[STR_TICKER_MAX] = "XMR";
+ const char* filter = filter_buf;
+
+ if (argc == 4 && strcmp(argv[2], "-t") == 0) {
+ if (strlen(argv[3]) >= STR_TICKER_MAX) {
+ fprintf(stderr, "error: ticker too long\n");
+ return EXIT_FAILURE;
+ }
+ strncpy(filter_buf, argv[3], STR_TICKER_MAX - 1);
+ filter_buf[STR_TICKER_MAX - 1] = '\0';
+ normalize_ticker(filter_buf);
+ }
+
+ openalias_record results[MAX_RECORDS] = { 0 };
+ size_t count = 0;
+
+#ifdef _WIN32
+ DNS_RECORDA* records = NULL;
+ DNS_STATUS status = DnsQuery_A(
+ fqdn,
+ DNS_TYPE_TEXT,
+ DNS_QUERY_STANDARD,
+ NULL,
+ (PDNS_RECORD*) & records,
+ NULL
+ );
+
+ if (status != ERROR_SUCCESS) {
+ fprintf(stderr, "error: dns txt lookup failed (status %lu)\n", status);
+ return EXIT_FAILURE;
+ }
+
+ for (DNS_RECORDA* r = records; r && count < MAX_RECORDS; r = r->pNext) {
+ if (r->wType != DNS_TYPE_TEXT) continue;
+
+ char txt[STR_MAX] = { 0 };
+ size_t pos = 0;
+
+ for (DWORD i = 0; i < r->Data.TXT.dwStringCount; i++) {
+ const char* seg = r->Data.TXT.pStringArray[i];
+ size_t seglen = strlen(seg);
+ if (pos + seglen >= STR_MAX) break;
+ memcpy(txt + pos, seg, seglen);
+ pos += seglen;
+ }
+ txt[pos] = '\0';
+
+ if (parse_openalias(txt, &results[count], filter))
+ count++;
+ }
+
+ DnsRecordListFree(records, DnsFreeRecordList);
+
+#else
+ unsigned char answer[DNS_BUF_SIZE];
+ int len = res_query(fqdn, ns_c_in, ns_t_txt, answer, sizeof(answer));
+ if (len < 0) {
+ fprintf(stderr, "error: dns txt lookup failed (errno %d)\n", errno);
+ return EXIT_FAILURE;
+ }
+
+ ns_msg msg;
+ if (ns_initparse(answer, len, &msg) < 0) {
+ fprintf(stderr, "error: dns parse failed\n");
+ return EXIT_FAILURE;
+ }
+
+ int rr_count = ns_msg_count(msg, ns_s_an);
+ for (int i = 0; i < rr_count && count < MAX_RECORDS; i++) {
+ ns_rr rr;
+ if (ns_parserr(&msg, ns_s_an, i, &rr) != 0) continue;
+ if (ns_rr_type(rr) != ns_t_txt) continue;
+
+ const unsigned char* rdata = ns_rr_rdata(rr);
+ int rdlen = ns_rr_rdlen(rr);
+ char txt[STR_MAX] = { 0 };
+ size_t pos = 0;
+
+ for (int off = 0; off < rdlen;) {
+ int seglen = rdata[off++];
+ if (seglen <= 0 || off + seglen > rdlen || pos + seglen >= STR_MAX) break;
+ memcpy(txt + pos, rdata + off, seglen);
+ pos += seglen;
+ off += seglen;
+ }
+ txt[pos] = '\0';
+
+ if (parse_openalias(txt, &results[count], filter))
+ count++;
+ }
+#endif
+
+ if (count == 0) {
+ fprintf(stderr, "error: no openalias records found for %s\n", filter);
+ return EXIT_FAILURE;
+ }
+
+ for (size_t i = 0; i < count; i++)
+ puts(results[i].address);
+
+ return EXIT_SUCCESS;
+}
+
diff --git a/premake5.lua b/premake5.lua
new file mode 100644
index 0000000..2f7fbdc
--- /dev/null
+++ b/premake5.lua
@@ -0,0 +1,30 @@
+-- premake5.lua
+workspace "openalias"
+ architecture "x64"
+ configurations { "Debug", "Release" }
+ startproject "openalias"
+
+project "openalias"
+ kind "ConsoleApp"
+ language "C"
+ targetdir "bin/%{cfg.buildcfg}"
+
+ files { "openalias.c" }
+
+ filter "system:windows"
+ defines {"_WIN32" }
+ links { "dnsapi" }
+ systemversion "latest"
+
+ filter "system:linux"
+ defines { "_POSIX_C_SOURCE=200112L" }
+ links { "resolv" }
+ buildoptions { "-Wall", "-Wextra" }
+
+ filter "configurations:Debug"
+ defines { "DEBUG" }
+ symbols "On"
+
+ filter "configurations:Release"
+ defines { "NDEBUG" }
+ optimize "On"