dinoco

Query DNS records
git clone git://git.relim.de/dinoco.git
Log | Files | Refs | README | LICENSE

commit f6d34477c3eaa4352384cccd8c3e2dd7697e69c3
Author: Nibo <kroekerrobin@gmail.com>
Date:   Tue, 20 Jun 2023 17:22:31 +0200

Initial commit

Diffstat:
A.gitignore | 1+
AMakefile | 17+++++++++++++++++
Adinoco.1 | 13+++++++++++++
Adinoco.c | 535+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adinoco.h | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 675 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +dinoco diff --git a/Makefile b/Makefile @@ -0,0 +1,17 @@ +PREFIX = /usr/local +MANPREFIX = $(PREFIX)/share/man + +all: + $(CC) -O -Werror -o dinoco dinoco.c +clean: + rm dinoco +install: all + mkdir -p "$(PREFIX)/bin" + cp -f dinoco "$(PREFIX)/bin" + chmod 755 "$(PREFIX)/bin/dinoco" + mkdir -p "$(MANPREFIX)/man1" + cp -f dinoco.1 "$(MANPREFIX)/man1/dinoco.1" + chmod 644 "$(MANPREFIX)/man1/dinoco.1" +uninstall: + rm "$(PREFIX)/bin/dinoco" + rm "$(MANPREFIX)/man1/dinoco.1" diff --git a/dinoco.1 b/dinoco.1 @@ -0,0 +1,13 @@ +.TH DINOCO "1" "June 2023" "User Commands" +.SH NAME +dinoco - Query DNS records +.SH SYNOPSIS +.B dinoco +[ options ] +.SH DESCRIPTION +.PP +Query DNS records +.SH EXAMPLES +.sp +.RS 4 +dinoco diff --git a/dinoco.c b/dinoco.c @@ -0,0 +1,535 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> +#include <getopt.h> +#include <ctype.h> +#include <sys/socket.h> +#include <netdb.h> +#include <arpa/inet.h> +#include "dinoco.h" + +char *stringCat(const char *str1, const char *str2) +{ + int str1Len = strlen(str1); + int str2Len = strlen(str2); + char *string = malloc((str1Len+str2Len+1) * sizeof(char)); + int i = 0; + int k = 0; + for (; i<str1Len; i++) + { + string[i] = str1[i]; + } + for (; k<str2Len; k++) + { + string[i+k] = str2[k]; + } + string[i+k] = '\0'; + return string; +} + +char *getRCODEString(enum rcode code) +{ + switch (code) + { + case RCODE_FORMAT_ERROR: + return "Format Error"; + break; + case RCODE_SERVER_FAILURE: + return "Server Failure"; + break; + case RCODE_NAME_ERROR: + return "Name Error"; + break; + case RCODE_NOT_IMPLEMENTED: + return "Not Implemented"; + break; + case RCODE_REFUSED: + return "Refused"; + break; + default: + return ""; + } +} + +char *getDNSServerIP() +{ + const char *cmd = "cat /etc/resolv.conf | grep 'nameserver.*\\..*' | cut -d' ' -f2 | tr -d '\n'"; + FILE *fp= popen(cmd, "r"); + char *buf = malloc(16 * sizeof(char)); + size_t bytesRead = fread(buf, 1, 15, fp); + fclose(fp); + if (bytesRead >= MIN_IP_LENGTH) + { + buf[bytesRead] = 0; + struct sockaddr_in sa; + int ret = inet_pton(AF_INET, buf, &(sa.sin_addr)); + if (ret != 0) + return buf; + else + return ""; + } + return ""; +} + +char *toUpper(char *string) +{ + char *newString = malloc((strlen(string)+1) * sizeof(char)); + int i = 0; + for (; i<strlen(string); i++) + { + newString[i] = toupper(string[i]); + } + newString[i] = 0; + return newString; +} + +short parseType(char *arg) +{ + for (int i=0; i<16; i++) + { + if (strcmp(arg, types[i]) == 0) + return i+1; + } + return -1; +} + +// e.g. "domain.com" -> 6, 'd', 'o', 'm', 'a', 'i', 'n', 3, 'c', 'o', 'm', 0 +struct byte_array *formDomain(char *domain) +{ + char *dnsDomain = malloc(sizeof(char)); + int i = 0; + int q = 0; + int domainLabelCharCount = 0; + char c; + while ((c = domain[i]) != '\0') + { + if (c == '.') + { + dnsDomain = realloc(dnsDomain, (q + 1) * sizeof(char)); + dnsDomain[q] = domainLabelCharCount; + q++; + for (int k=i-domainLabelCharCount; k<i; k++) + { + dnsDomain = realloc(dnsDomain, (q + 1) * sizeof(char)); + dnsDomain[q] = domain[k]; + q++; + } + domainLabelCharCount = 0; + } + else + { + domainLabelCharCount++; + } + i++; + } + dnsDomain = realloc(dnsDomain, (q + 1) * sizeof(char)); + dnsDomain[q] = domainLabelCharCount; + q++; + for (int k=i-domainLabelCharCount; k<i; k++) + { + dnsDomain = realloc(dnsDomain, (q + 1) * sizeof(char)); + dnsDomain[q] = domain[k]; + q++; + } + dnsDomain = realloc(dnsDomain, (q + 1) * sizeof(char)); + dnsDomain[q] = 0; + dnsDomain = realloc(dnsDomain, (q + 1 + 4) * sizeof(char)); + struct byte_array *b = malloc(sizeof(struct byte_array)); + b->bytes = dnsDomain; + b->length = q + 1; + return b; +} + +char *parseDomain(struct byte_array *res, int start, int *nameLength) +{ + static int recursionLevel = 0; + recursionLevel++; + char *domain = malloc(sizeof(char)); + char *string; + int offset = 0; + int k = 0; + int i = start; + while (res->bytes[i] != 0) + { + // If pointer + if (res->bytes[i] & 0b11000000) + { + if (recursionLevel == 1) + *nameLength += 2; + offset = res->bytes[i+1]; + string = parseDomain(res, offset, nameLength); + domain[k] = 0; + domain = stringCat(domain, string); + return domain; + } + if (recursionLevel == 1) + *nameLength++; + for (int s=0; s<res->bytes[i]; s++) + { + if (recursionLevel == 1) + *nameLength++; + domain[k] = res->bytes[i+s+1]; + domain = realloc(domain, (k+1) * sizeof(char)); + k++; + } + i += res->bytes[i] + 1; + if (res->bytes[i] != 0) + { + domain[k] = '.'; + domain = realloc(domain, (k+1) * sizeof(char)); + k++; + } + } + domain[k] = 0; + return domain; +} + +struct byte_array *formHeader(struct dns_header *h) +{ + struct byte_array *header = malloc(sizeof(struct byte_array)); + header->bytes = malloc(DNS_HEADER_LENGTH * sizeof(char)); + header->length = DNS_HEADER_LENGTH; + header->bytes[0] = (h->id >> 8) & 0xFF; + header->bytes[1] = h->id & 0xFF; + char flagOne = 0; + if (h->qr) + flagOne = flagOne | 0b10000000; + switch (h->opcode) + { + case OPCODE_QUERY: + // The bit we would set to zero is already zero + break; + case OPCODE_IQUERY: + flagOne = flagOne | 0b10001000; + break; + case OPCODE_STATUS: + flagOne = flagOne | 0b10010000; + break; + } + if (h->aa) + flagOne = flagOne | 0b00000100; + if (h->tc) + flagOne = flagOne | 0b00000010; + if (h->rd) + flagOne = flagOne | 0b00000001; + header->bytes[2] = flagOne; + char flagTwo = 0; + if (h->ra) + flagTwo = flagTwo | 0b10000000; + header->bytes[3] = flagTwo; + header->bytes[4] = (h->qdcount >> 8) & 0xFF; + header->bytes[5] = h->qdcount & 0xFF; + header->bytes[6] = (h->ancount >> 8) & 0xFF; + header->bytes[7] = h->ancount & 0xFF; + header->bytes[8] = (h->nscount >> 8) & 0xFF; + header->bytes[9] = h->nscount & 0xFF; + header->bytes[10] = (h->arcount >> 8) & 0xFF; + header->bytes[11] = h->arcount & 0xFF; + return header; +} + +struct byte_array *formQuestion(struct dns_question *qu) +{ + struct byte_array *question = malloc(sizeof(struct byte_array)); + struct byte_array *d = formDomain(qu->domain); + int i = 0; + for (; i<d->length; i++) + { + question->bytes = realloc(question->bytes, (i+1) * sizeof(char)); + question->bytes[i] = d->bytes[i]; + } + question->length = d->length; + question->bytes = realloc(question->bytes, (i+4) * sizeof(char)); + question->bytes[i] = 0x00; + question->bytes[i+1] = qu->type; + question->bytes[i+2] = 0x00; + question->bytes[i+3] = qu->class; + question->length = d->length+4; + return question; +} + +struct dns_header *parseHeader(char *res) +{ + struct dns_header *header = malloc(sizeof(struct dns_header)); + header->id = res[1] + 256U*res[0]; + header->qr = res[2] & 0b10000000 ? true : false; + if (res[2] & 0b00001000) + { + header->opcode = OPCODE_IQUERY; + } + else if (res[2] & 0b00010000) + { + header->opcode = OPCODE_STATUS; + } + else { + header->opcode = OPCODE_QUERY; + } + header->aa = res[2] & 0b00000100 ? true : false; + header->tc = res[2] & 0b00000010 ? true : false; + header->rd = res[2] & 0b00000001 ? true : false; + header->ra = res[3] & 0b10000000 ? true : false; + int rcode = 0; + if (res[3] & 0b00000001) + rcode++; + if (res[3] & 0b00000010) + rcode += 2; + if (res[3] & 0b00000100) + rcode += 4; + header->rcode = rcode; + header->qdcount = res[5] + 256U*res[4]; + header->ancount = res[7] + 256U*res[6]; + header->nscount = res[9] + 256U*res[8]; + header->arcount = res[11] + 256U*res[10]; + return header; +} + +struct dns_resource_record *parseAnswer(struct byte_array *res, int *start) +{ + struct dns_resource_record *answer = malloc(sizeof(struct dns_resource_record)); + int nameLength = 0; + answer->domain = parseDomain(res, *start, &nameLength); + int b = *start + nameLength; + answer->type = res->bytes[b+1]; + answer->class = res->bytes[b+3]; + answer->ttl = res->bytes[b+7] + + 256U*res->bytes[b+6] + + 65536U*res->bytes[b+5] + + 16777216U*res->bytes[b+4]; + answer->rdlength = res->bytes[b+9] + + 256U*res->bytes[b+8]; + answer->rdata = malloc(sizeof(struct byte_array)); + answer->rdata->bytes = malloc(sizeof(char)); + answer->rdata->length = answer->rdlength; + int i = 0; + for (; i<answer->rdlength; i++) + { + answer->rdata->bytes[i] = res->bytes[b+10+i]; + answer->rdata->bytes = realloc(answer->rdata->bytes, (i+1) * sizeof(char)); + } + *start += nameLength + 8 + answer->rdlength; + return answer; +} + +void printAnswer(struct dns_resource_record *answer, enum type type, struct byte_array *res) +{ + switch (type) + { + case TYPE_A: + printf("Address: "); + for (int i=0; i<answer->rdlength; i++) + { + printf("%d", answer->rdata->bytes[i] & 0xFF); + if (i != answer->rdlength-1) + printf("."); + } + printf("\n"); + break; + case TYPE_NS: + break; + case TYPE_MD: + break; + case TYPE_MF: + break; + case TYPE_CNAME: + break; + case TYPE_SOA: + break; + case TYPE_MB: + break; + case TYPE_MG: + break; + case TYPE_MR: + break; + case TYPE_NULL: + break; + case TYPE_WKS: + break; + case TYPE_PTR: + break; + case TYPE_HINFO: + break; + case TYPE_MINFO: + break; + case TYPE_MX: + uint16_t preference = answer->rdata->bytes[1] + + 256U*answer->rdata->bytes[0]; + int useless = 0; + int startOfMailDomain = res->length - answer->rdlength + 2; + char *mailDomain = parseDomain(res, startOfMailDomain, &useless); + printf("Preference: %d\n", preference); + printf("Mail Exchange: %s\n", mailDomain); + break; + case TYPE_TXT: + break; + } +} + +struct byte_array *reqServer(char *req, int length) +{ + char *ip = getDNSServerIP(); + if (strlen(ip) == 0) + { + printf("getDNSServerIP failed.\n"); + return NULL; + } + struct hostent *host = (struct hostent *) gethostbyname(ip); + int port = 53; + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) + { + perror("socket failed.\n"); + return NULL; + } + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr = *((struct in_addr *)host->h_addr); + bzero(&(addr.sin_zero), 8); + socklen_t size = (socklen_t) sizeof(addr); + size_t bytesSent = sendto(fd, req, length, 0, (struct sockaddr *) &addr, size); + if (bytesSent == length) + { + char *res = malloc(200 * sizeof(char)); + size_t bytesReceived = recvfrom(fd, res, 200, 0, (struct sockaddr *) &addr, &size); + if (bytesReceived > 0) + { + struct byte_array *response = malloc(sizeof(struct byte_array)); + response->bytes = res; + response->length = bytesReceived; + return response; + } + else + { + printf("Didn't receive a response.\n"); + return NULL; + } + } + else + { + printf("Didn't send whole request.\n"); + return NULL; + } +} + +bool isValidResponse(struct dns_header *reqHeader, struct dns_header *resHeader) +{ + if (reqHeader->id != resHeader->id) + return false; + if (resHeader->ancount < reqHeader->qdcount) + return false; + if (resHeader->rcode != RCODE_NO_ERROR) + { + printf("%s\n", getRCODEString(resHeader->rcode)); + return false; + } + return true; +} + +int main(int argc, char *argv[]) +{ + static struct option options[] = { + { "domain", required_argument, 0, 'd' }, + { "type", required_argument, 0, 't' }, + { 0, 0, 0, 0 } + }; + int optionIndex = 0; + int o = 0; + char *domain; + char *type; + bool isDomain = false; + bool isType = false; + while ((o = getopt_long(argc, argv, "d:t:", options, &optionIndex)) != -1) + { + switch (o) + { + case 'd': + domain = malloc((strlen(optarg) + 1) * sizeof(char)); + strcpy(domain, optarg); + isDomain = true; + break; + case 't': + type = malloc((strlen(optarg)+1) * sizeof(char)); + strcpy(type, optarg); + isType = true; + break; + } + } + if (!isDomain || !isType) + { + printf("You have to set both the domain (-d/--domain) and the type (-t/--type).\n"); + return -1; + } + short tp = parseType(toUpper(type)); + if (tp == -1) + { + printf("You provided an invalid type.\n"); + return -1; + } + enum type t = tp; + struct byte_array *reqHeader = formHeader(&DNS_HEADER_DEFAULT); + char *req = malloc(reqHeader->length * sizeof(char)); + int i = 0; + for (; i<reqHeader->length; i++) + { + req[i] = reqHeader->bytes[i]; + } + struct dns_question qu = { + .domain = domain, + .type = t, + .class = CLASS_IN + }; + struct byte_array *question = formQuestion(&qu); + req = realloc(req, (reqHeader->length + question->length) * sizeof(char)); + for (int e=0; e<question->length; e++) + { + req[i] = question->bytes[e]; + i++; + } + int length = reqHeader->length + question->length; + struct byte_array *res = reqServer(req, length); + if (res == NULL) + { + printf("reqServer failed.\n"); + return -1; + } + if (res->length >= DNS_HEADER_LENGTH) + { + /* printf("res:\n"); + for (int i=0; i<res->length; i++) + { + printf("%d: %02X\n", i, res->bytes[i]); + } + printf("\n"); */ + struct dns_header *resHeader = parseHeader(res->bytes); + if (isValidResponse(&DNS_HEADER_DEFAULT, resHeader)) + { + int startOfAnswer = DNS_HEADER_LENGTH + question->length; + /* + This doesn't mean it's long enough to parse an answer + but there is at least part of an answer. + */ + if (res->length > startOfAnswer) + { + struct dns_resource_record *answer = malloc(sizeof(struct dns_resource_record)); + for (int i=0; i<resHeader->ancount; i++) + { + answer = parseAnswer(res, &startOfAnswer); + printAnswer(answer, t, res); + } + } + } + else + { + printf("isValidResponse failed.\n"); + return -1; + } + } + else + { + printf("Response too short.\n"); + return -1; + } + return 0; +} diff --git a/dinoco.h b/dinoco.h @@ -0,0 +1,109 @@ +#define DNS_HEADER_LENGTH 12 +#define MIN_IP_LENGTH 7 + +struct byte_array +{ + char *bytes; + int length; +}; + +enum type +{ + TYPE_A = 1, + TYPE_NS, + TYPE_MD, + TYPE_MF, + TYPE_CNAME, + TYPE_SOA, + TYPE_MB, + TYPE_MG, + TYPE_MR, + TYPE_NULL, + TYPE_WKS, + TYPE_PTR, + TYPE_HINFO, + TYPE_MINFO, + TYPE_MX, + TYPE_TXT +}; + +const char *types[] = { + "A", "NS", "MD", "MF", + "CNAME", "SOA", "MB", "MG", + "MR", "NULL", "WKS", "PTR", + "HINFO", "MINFO", "MX", "TXT" +}; + +enum class +{ + CLASS_IN = 1, + CLASS_CS, + CLASS_CH, + CLASS_HS +}; + +enum opcode +{ + OPCODE_QUERY, + OPCODE_IQUERY, + OPCODE_STATUS +}; + +enum rcode +{ + RCODE_NO_ERROR, + RCODE_FORMAT_ERROR, + RCODE_SERVER_FAILURE, + RCODE_NAME_ERROR, + RCODE_NOT_IMPLEMENTED, + RCODE_REFUSED +}; + +struct dns_header +{ + uint16_t id; // char ID[2]; + bool qr; + enum opcode opcode; + bool aa; + bool tc; + bool rd; + bool ra; + enum rcode rcode; + uint16_t qdcount; // char QDCOUNT[2]; + uint16_t ancount; // char ANCOUNT[2]; + uint16_t nscount; // char NSCOUNT[2]; + uint16_t arcount; // char ARCOUNT[2]; +}; + +struct dns_header DNS_HEADER_DEFAULT = { + .id = 55, + .qr = false, + .opcode = OPCODE_QUERY, + .aa = false, + .tc = false, + .rd = true, + .ra = false, + .rcode = RCODE_NO_ERROR, + .qdcount = 1, + .ancount = 0, + .nscount = 0, + .arcount = 0 +}; + +struct dns_question +{ + char *domain; + enum type type; + enum class class; +}; + +// Either Answer, Authority or Additional +struct dns_resource_record +{ + char *domain; + enum type type; + enum class class; + uint32_t ttl; + uint16_t rdlength; + struct byte_array *rdata; +};