commit f6d34477c3eaa4352384cccd8c3e2dd7697e69c3
Author: Nibo <kroekerrobin@gmail.com>
Date: Tue, 20 Jun 2023 17:22:31 +0200
Initial commit
Diffstat:
| A | .gitignore | | | 1 | + |
| A | Makefile | | | 17 | +++++++++++++++++ |
| A | dinoco.1 | | | 13 | +++++++++++++ |
| A | dinoco.c | | | 535 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | dinoco.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;
+};