#define _CRT_SECURE_NO_WARNINGS #include #include #include #include #ifdef _WIN32 #include #include #pragma comment(lib, "dnsapi.lib") #else #include #include #include #include #include #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 [-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; }