dinoco

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

commit e2d962c8ad3bf0d3f7ccd4d524d2615861cb45f4
parent 2589aef0eedb0c9a937b93ce6af68ee294bd1ad7
Author: Nibo <kroekerrobin@gmail.com>
Date:   Thu, 22 Jun 2023 22:42:09 +0200

Satisfy valgrind

Diffstat:
MMakefile | 2+-
Mdinoco.1 | 25+++++++++++++++++++------
Mdinoco.c | 224++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mdinoco.h | 17+++++++++++++++++
4 files changed, 214 insertions(+), 54 deletions(-)

diff --git a/Makefile b/Makefile @@ -2,7 +2,7 @@ PREFIX = /usr/local MANPREFIX = $(PREFIX)/share/man all: - $(CC) -O -Werror -o dinoco dinoco.c + $(CC) -g -O -Werror -o dinoco dinoco.c clean: rm dinoco install: all diff --git a/dinoco.1 b/dinoco.1 @@ -1,10 +1,15 @@ -.TH DINOCO 1" "June 2023" "User Commands" +.TH "DINOCO 1" "June 2023" "User Commands" .SH NAME dinoco - Query DNS records .SH SYNOPSIS .B dinoco --d \fI\,domain\/\fR --t \fI\,type\/\fR +.B -d +\fI\,domain\/\fR +.B -t +\fI\,type\/\fR +[ +.B -h +] .SH DESCRIPTION .PP Query a DNS record or possibly multiple records. The DNS class will always be IN (the Internet). Only the RDATA field of the answer will be printed to stdout according to the DNS type. @@ -16,12 +21,20 @@ A domain. A DNS type. One of the following: A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT. +.TP +\fB\,-h\/\fR +Disable the printing of the header line. .SH EXAMPLES .EX $ dinoco -d blubiblub.org -t MX -Preference: 10 -Mail Exchange: mail.blubiblub.org +Mail Exchange Preference +mail.blubiblub.org 10 $ $ dinoco -d blubiblub.org -t A -Address: 221.180.67.12 +Address +221.180.67.12 +$ +$ dinoco -d honkaponka.org -t MX -h +mail.honkaponla.org 10 +alt1.mail.honkaponla.org 20 $ diff --git a/dinoco.c b/dinoco.c @@ -10,7 +10,7 @@ #include <arpa/inet.h> #include "dinoco.h" -char *stringCat(const char *str1, const char *str2) +inline char *stringCat(char *str1, char *str2) { int str1Len = strlen(str1); int str2Len = strlen(str2); @@ -26,6 +26,8 @@ char *stringCat(const char *str1, const char *str2) string[i+k] = str2[k]; } string[i+k] = '\0'; + free(str1); + free(str2); return string; } @@ -56,7 +58,7 @@ char *getRCODEString(enum rcode code) char *getDNSServerIP() { const char *cmd = "cat /etc/resolv.conf | grep 'nameserver.*\\..*' | cut -d' ' -f2 | tr -d '\n'"; - FILE *fp= popen(cmd, "r"); + FILE *fp = popen(cmd, "r"); char *buf = malloc(16 * sizeof(char)); size_t bytesRead = fread(buf, 1, 15, fp); fclose(fp); @@ -82,6 +84,7 @@ char *toUpper(char *string) newString[i] = toupper(string[i]); } newString[i] = 0; + free(string); return newString; } @@ -146,12 +149,11 @@ struct byte_array *formDomain(char *domain) return b; } -char *parseDomain(struct byte_array *res, int start, int *nameLength) +char *parseDomain(struct byte_array *res, int start) { - static int recursionLevel = 0; - recursionLevel++; char *domain = NULL; - char *string; + char *mergedDomain = NULL; + char *string = NULL; int offset = 0; int k = 0; int i = start; @@ -160,21 +162,15 @@ char *parseDomain(struct byte_array *res, int start, int *nameLength) // If pointer if (res->bytes[i] & 0b11000000) { - if (recursionLevel == 1) - *nameLength += 2; offset = res->bytes[i+1]; - string = parseDomain(res, offset, nameLength); + string = parseDomain(res, offset); domain = realloc(domain, (k+1) * sizeof(char)); domain[k] = 0; - domain = stringCat(domain, string); - return domain; + mergedDomain = stringCat(domain, string); + return mergedDomain; } - if (recursionLevel == 1) - *nameLength++; for (int s=0; s<res->bytes[i]; s++) { - if (recursionLevel == 1) - *nameLength++; domain = realloc(domain, (k+1) * sizeof(char)); domain[k] = res->bytes[i+s+1]; k++; @@ -186,6 +182,10 @@ char *parseDomain(struct byte_array *res, int start, int *nameLength) domain[k] = '.'; k++; } + else + { + break; + } } domain = realloc(domain, (k+1) * sizeof(char)); domain[k] = 0; @@ -239,6 +239,7 @@ struct byte_array *formHeader(struct dns_header *h) struct byte_array *formQuestion(struct dns_question *qu) { struct byte_array *question = malloc(sizeof(struct byte_array)); + question->bytes = NULL; struct byte_array *d = formDomain(qu->domain); int i = 0; for (; i<d->length; i++) @@ -293,12 +294,27 @@ struct dns_header *parseHeader(char *res) return header; } +int findNameLength(struct byte_array *res, int start) +{ + int length = 0; + int i = start; + while (res->bytes[i] != 0) + { + if ((res->bytes[i] & 0xFF) == 0xC0) + { + length += 2; + break; + } + length++; + } + return length; +} + struct dns_resource_record *parseAnswer(struct byte_array *res, int *start) { - printf("soa: %d\n", *start); struct dns_resource_record *answer = malloc(sizeof(struct dns_resource_record)); - int nameLength = 0; - answer->domain = parseDomain(res, *start, &nameLength); + answer->domain = parseDomain(res, *start); + int nameLength = findNameLength(res, *start); int b = *start + nameLength; answer->type = res->bytes[b+1]; answer->class = res->bytes[b+3]; @@ -312,32 +328,52 @@ struct dns_resource_record *parseAnswer(struct byte_array *res, int *start) answer->rdata->bytes = NULL; answer->rdata->length = answer->rdlength; int i = 0; - printf("moa.\n"); for (; i<answer->rdlength; i++) { answer->rdata->bytes = realloc(answer->rdata->bytes, (i+1) * sizeof(char)); answer->rdata->bytes[i] = res->bytes[b+10+i]; } - *start += nameLength + 8 + answer->rdlength; - *start += 2; - printf("eoa.\n"); + *start += nameLength + 10 + answer->rdlength; return answer; } -void printAnswer(struct dns_resource_record *answer, enum type type, struct byte_array *res) +union dns_type_result *parseAnswerByType +( + struct dns_resource_record *answer, + enum type type, + struct byte_array *res, + int *startOfNextAnswer +) { - printf("sop.\n"); + union dns_type_result *ur = malloc(sizeof(union dns_type_result)); switch (type) { case TYPE_A: - printf("Address: "); + ur->a = malloc(sizeof(struct dns_a_result)); + ur->a->ipAddress = NULL; + int e = 0; + uint8_t ipPart = 0; for (int i=0; i<answer->rdlength; i++) { - printf("%d", answer->rdata->bytes[i] & 0xFF); - if (i != answer->rdlength-1) - printf("."); + ipPart = answer->rdata->bytes[i] & 0xFF; + char number[4]; + sprintf(number, "%d", ipPart); + number[3] = 0; + for (int k=0; k<strlen(number); k++) + { + ur->a->ipAddress = realloc(ur->a->ipAddress, (e+1) * sizeof(char)); + ur->a->ipAddress[e] = number[k]; + e++; + } + if (i != 3) + { + ur->a->ipAddress = realloc(ur->a->ipAddress, (e+1) * sizeof(char)); + ur->a->ipAddress[e] = '.'; + e++; + } } - printf("\n"); + ur->a->ipAddress = realloc(ur->a->ipAddress, (e+1) * sizeof(char)); + ur->a->ipAddress[e] = 0; break; case TYPE_NS: break; @@ -368,12 +404,10 @@ void printAnswer(struct dns_resource_record *answer, enum type type, struct byte 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); - free(mailDomain); + int startOfMailDomain = *startOfNextAnswer - answer->rdlength + 2; + ur->mx = malloc(sizeof(struct dns_mx_result)); + ur->mx->preference = preference; + ur->mx->mailDomain = parseDomain(res, startOfMailDomain); break; case TYPE_TXT: break; @@ -382,7 +416,90 @@ void printAnswer(struct dns_resource_record *answer, enum type type, struct byte free(answer->rdata); free(answer->domain); free(answer); - printf("eop.\n"); + return ur; +} + +void printAnswers +( + union dns_type_result **answers, + int answerCount, + enum type type, + struct byte_array *res, + int *startOfNextAnswer, + bool disableHeader +) +{ + switch (type) + { + case TYPE_A: + if (!disableHeader) + printf("Address\n"); + for (int i=0; i<answerCount; i++) + { + printf("%s\n", answers[i]->a->ipAddress); + } + for (int i=0; i<answerCount; i++) + { + free(answers[i]->a->ipAddress); + free(answers[i]->a); + free(answers[i]); + } + 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: + size_t longestMailDomain = 0; + size_t len = 0; + for (int i=0; i<answerCount; i++) + { + if ((len = strlen(answers[i]->mx->mailDomain)) > longestMailDomain) + longestMailDomain = len; + } + if (!disableHeader) + printf("%-*s\t%s\n", longestMailDomain, "Mail Exchange", "Preference"); + for (int i=0; i<answerCount; i++) + { + printf( + "%s\t%d\n", + answers[i]->mx->mailDomain, + answers[i]->mx->preference + ); + } + for (int i=0; i<answerCount; i++) + { + free(answers[i]->mx->mailDomain); + free(answers[i]->mx); + free(answers[i]); + } + break; + case TYPE_TXT: + break; + } + free(answers); } struct byte_array *reqServer(char *req, int length) @@ -422,15 +539,16 @@ struct byte_array *reqServer(char *req, int length) response->bytes = realloc(response->bytes, (i+1) * sizeof(char)); response->bytes[i] = res[i]; } - response->length = bytesReceived; + response->length = bytesReceived; + free(res); return response; } else { + free(res); printf("Didn't receive a response.\n"); return NULL; } - free(res); } else { @@ -458,6 +576,7 @@ int main(int argc, char *argv[]) static struct option options[] = { { "domain", required_argument, 0, 'd' }, { "type", required_argument, 0, 't' }, + { "header", no_argument, 0, 'h' }, { 0, 0, 0, 0 } }; int optionIndex = 0; @@ -466,7 +585,8 @@ int main(int argc, char *argv[]) char *type; bool isDomain = false; bool isType = false; - while ((o = getopt_long(argc, argv, "d:t:", options, &optionIndex)) != -1) + bool disableHeader = false; + while ((o = getopt_long(argc, argv, "d:t:h", options, &optionIndex)) != -1) { switch (o) { @@ -480,6 +600,9 @@ int main(int argc, char *argv[]) strcpy(type, optarg); isType = true; break; + case 'h': + disableHeader = true; + break; } } if (!isDomain || !isType) @@ -526,34 +649,41 @@ int main(int argc, char *argv[]) } if (res->length >= DNS_HEADER_LENGTH) { - printf("res:\n"); + /* printf("res:\n"); for (int i=0; i<res->length; i++) { printf("%d: %02X\n", i, res->bytes[i]); } - printf("\n"); + printf("\n"); */ struct dns_header *resHeader = parseHeader(res->bytes); if (isValidResponse(&DNS_HEADER_DEFAULT, resHeader)) { - int startOfAnswer = DNS_HEADER_LENGTH + question->length; + int answerCount = resHeader->ancount; + int *startOfAnswer = malloc(sizeof(int)); + *startOfAnswer = DNS_HEADER_LENGTH + question->length; free(question->bytes); free(question); + free(resHeader); /* 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) + if (res->length > *startOfAnswer) { struct dns_resource_record *answer; - for (int i=0; i<resHeader->ancount; i++) + union dns_type_result **answers = NULL; + for (int i=0; i<answerCount; i++) { - answer = parseAnswer(res, &startOfAnswer); - printAnswer(answer, t, res); + answer = parseAnswer(res, startOfAnswer); + union dns_type_result *r = parseAnswerByType(answer, t, res, startOfAnswer); + answers = realloc(answers, (i+1) * sizeof(union dns_type_result)); + answers[i] = r; } + printAnswers(answers, answerCount, t, res, startOfAnswer, disableHeader); } + free(startOfAnswer); free(res->bytes); free(res); - free(resHeader); } else { diff --git a/dinoco.h b/dinoco.h @@ -108,3 +108,20 @@ struct dns_resource_record uint16_t rdlength; struct byte_array *rdata; }; + +struct dns_mx_result +{ + uint16_t preference; + char *mailDomain; +}; + +struct dns_a_result +{ + char *ipAddress; +}; + +union dns_type_result +{ + struct dns_mx_result *mx; + struct dns_a_result *a; +};