diff options
| author | dmlunar <root@lunar.sh> | 2026-01-23 15:21:03 +0200 |
|---|---|---|
| committer | dmlunar <root@lunar.sh> | 2026-01-23 15:21:03 +0200 |
| commit | 506c89c969d932ae75482f89d28b1a33c27fc9e2 (patch) | |
| tree | 4052c3b854e06653836904bdb80be3716428b551 | |
| download | openresolve-506c89c969d932ae75482f89d28b1a33c27fc9e2.tar.gz openresolve-506c89c969d932ae75482f89d28b1a33c27fc9e2.zip | |
openalias: initial commit
Initial commit.
| -rw-r--r-- | openalias.c | 242 | ||||
| -rw-r--r-- | premake5.lua | 30 |
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" |
