summaryrefslogtreecommitdiffstats
path: root/dns.c
diff options
context:
space:
mode:
Diffstat (limited to 'dns.c')
-rw-r--r--dns.c244
1 files changed, 244 insertions, 0 deletions
diff --git a/dns.c b/dns.c
new file mode 100644
index 0000000..97ea778
--- /dev/null
+++ b/dns.c
@@ -0,0 +1,244 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <unistd.h>
+#include "domain2name.c" // this is from http://git.sijanec.eu/sijanec/dnsfind
+enum type {
+ A = 1,
+ Ns,
+ Md,
+ Mf,
+ Cname,
+ Soa,
+ Mb,
+ M,
+ Mr,
+ Null,
+ Wks,
+ Ptr,
+ Hinfo,
+ Minfo,
+ Mx,
+ Txt
+};
+enum class {
+ In = 1,
+ Cs,
+ Ch,
+ He,
+ Any = 255
+};
+#define RESPONSE (1 << 15)
+#define QUESTION (0 << 15)
+#define QUERY (0 << 11)
+#define IQUERY (1 << 11)
+#define STATUS (1 << 12)
+#define AA (1 << 10)
+#define TC (1 << 9)
+#define RD (1 << 8)
+#define RA (1 << 7)
+#define SUCCESS (0 << 0)
+#define FORMAT_ERROR (1 << 0)
+#define SERVFAIL (1 << 1)
+#define NXDOMAIN (FORMAT_ERROR | SERVFAIL)
+#define NI (1 << 2)
+#define FORBIDDEN (NXDOMAIN | FORMAT_ERROR)
+struct header {
+ uint16_t xid __attribute__((packed));
+ uint16_t flags __attribute__((packed));
+ uint16_t qdcount __attribute__((packed));
+ uint16_t ancount __attribute__((packed));
+ uint16_t nscount __attribute__((packed));
+ uint16_t arcount __attribute__((packed));
+ char data[]; /* ignored for char[] */
+} __attribute__((packed));
+struct rr { /* name is omitted, first byte of struct is first byte of type */
+ uint16_t type __attribute__((packed));
+ uint16_t class __attribute__((packed));
+ uint32_t ttl __attribute__((packed));
+ uint16_t len __attribute__((packed));
+ char data[]; /* ignored for char[] */
+} __attribute__((packed));
+struct question {
+ uint16_t type __attribute__((packed));
+ uint16_t class __attribute__((packed));
+} __attribute__((packed));
+enum dns_loglevel {
+ DNS_DEBUG,
+ DNS_INFO,
+ DNS_WARN,
+ DNS_ERROR
+};
+typedef void (* dns_log_handler) (void * const, const enum dns_loglevel, const char * const, const char * const);
+struct dns {
+ int fd;
+ char * domain; // this is as it appears in the packet
+ dns_log_handler log_handler;
+ void * log_userdata;
+ struct sockaddr_in sockaddr;
+};
+typedef struct dns dns;
+static void dns_default_log_handler (void * const u __attribute__((unused)),
+ const enum dns_loglevel l, const char * a, const char * m) {
+ char * n = "unspec";
+ switch (l) {
+ case DNS_DEBUG:
+ n = "DEBUG";
+ break;
+ case DNS_INFO:
+ n = "INFO";
+ break;
+ case DNS_WARN:
+ n = "WARN";
+ break;
+ case DNS_ERROR:
+ n = "ERROR";
+ break;
+ }
+ fprintf(stderr, "%s %s %s\n", n, a, m);
+}
+struct dns * dns_init (void) {
+ struct dns * dns = calloc(1, sizeof(struct dns));
+ dns->fd = -1;
+ dns->domain = strdup(" call dns_set_domain to set the domain");
+ dns->domain[0] = strlen(dns->domain)-1;
+ dns->log_handler = dns_default_log_handler;
+ dns->sockaddr.sin_family = AF_INET;
+ dns->sockaddr.sin_port = htons(53);
+ dns->sockaddr.sin_addr.s_addr = INADDR_ANY;
+ return dns;
+}
+static void dns_set_domain (struct dns * dns, const char * domain) { // static functions are
+ int required = domain2name_len(domain, strlen(domain)); // visible inside the same
+ if (required <= 0) // translation unit - they
+ return; // are visible across
+ free(dns->domain); // included files
+ domain2name((dns->domain = malloc(required)), domain, strlen(domain));
+}
+static void dns_set_log_handler (struct dns * dns, dns_log_handler log_handler) {
+ if (!log_handler)
+ log_handler = dns_default_log_handler;
+ dns->log_handler = log_handler;
+}
+static void dns_set_log_userdata (struct dns * dns, void * log_userdata) {
+ dns->log_userdata = log_userdata;
+}
+static void dns_set_port (struct dns * dns, int port) {
+ dns->sockaddr.sin_port = htons(port);
+}
+static void dns_set_ip (struct dns * dns, const char * ip) {
+ if (!ip) {
+ dns->sockaddr.sin_addr.s_addr = INADDR_ANY;
+ return;
+ }
+ inet_aton(ip, &dns->sockaddr.sin_addr);
+}
+static void dns_run_once (struct dns * dns) {
+#define BUFLEN 4096
+ char buf[BUFLEN];
+ if (dns->fd == -1) {
+ if ((dns->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
+ sprintf(buf, "socket failed with %s", strerror(errno));
+ dns->log_handler(dns->log_userdata, DNS_ERROR, "dns", buf);
+ return;
+ }
+ int ž = 1;
+ if (setsockopt(dns->fd, SOL_SOCKET, SO_BROADCAST, &ž, sizeof(ž)) == -1) {
+ sprintf(buf, "setsockopt failed with %s", strerror(errno));
+ dns->log_handler(dns->log_userdata, DNS_ERROR, "dns", buf);
+ close(dns->fd);
+ dns->fd = -1;
+ return;
+ }
+ if (bind(dns->fd, (struct sockaddr *) &dns->sockaddr, sizeof(struct sockaddr))) {
+ sprintf(buf, "bind failed with %s", strerror(errno));
+ dns->log_handler(dns->log_userdata, DNS_ERROR, "dns", buf);
+ close(dns->fd);
+ dns->fd = -1;
+ return;
+ }
+ }
+ struct sockaddr_in sender;
+ socklen_t sendl = sizeof sender;
+ int size = recvfrom(dns->fd, buf, 65535, MSG_DONTWAIT, (struct sockaddr *) &sender, &sendl);
+ if (size == -1) {
+ if (errno != EWOULDBLOCK) {
+ sprintf(buf, "recvfrom failed with %s", strerror(errno));
+ dns->log_handler(dns->log_userdata, DNS_ERROR, "dns", buf);
+ close(dns->fd);
+ dns->fd = -1;
+ return;
+ }
+ return;
+ }
+ struct header * header = (struct header *) buf; // time for some overwriting
+ header->flags = htons(RESPONSE | QUERY | AA | SUCCESS);
+ header->ancount = htons(1); // we keep question number intact, as we send all qs back
+ header->nscount = 0;
+ header->arcount = 0;
+ buf[BUFLEN-1] = '\0'; // strstr and strlen on untrusted data!
+ char * ouranswerisat = memmem(buf, size, "in-addr", strlen("in-addr"));
+ if (!ouranswerisat) {
+ dns->log_handler(dns->log_userdata, DNS_INFO, "dns",
+ "received request without 'in-addr' string ... weird.");
+ return;
+ }
+ ouranswerisat += 17;
+ struct rr * rr = (struct rr *) (ouranswerisat + strlen(header->data) + 1);
+ if (ouranswerisat + strlen(header->data) + 1 + sizeof(struct rr)
+ + strlen(dns->domain) + 1 >= buf + BUFLEN) {
+ dns->log_handler(dns->log_userdata, DNS_WARN, "dns", "sent packet would be to big");
+ return;
+ }
+ strcpy(ouranswerisat, header->data);
+ rr->type = htons(Ptr);
+ rr->class = htons(In);
+ rr->ttl = 0;
+ rr->len = htons(strlen(dns->domain)+1);
+ strcpy(rr->data, dns->domain);
+ int len = rr->data - buf + strlen(dns->domain) + 1;
+ if (sendto(dns->fd, buf, len, 0, (struct sockaddr *) &sender, sizeof(sender)) == -1) {
+ sprintf(buf, "sendto failed with %s", strerror(errno));
+ dns->log_handler(dns->log_userdata, DNS_ERROR, "dns", buf);
+ close(dns->fd);
+ dns->fd = -1;
+ return;
+ }
+ char dst[INET_ADDRSTRLEN];
+ const char * resp = inet_ntop(AF_INET, &sender.sin_addr, dst, INET_ADDRSTRLEN);
+ sprintf(buf, "successfully sent DNS reply to %s", resp ? resp : "[inet_ntop failed]");
+ dns->log_handler(dns->log_userdata, DNS_ERROR, "dns", buf);
+}
+static void dns_free (struct dns * dns) {
+ if (dns->fd != -1)
+ close (dns->fd);
+ free(dns->domain);
+ free(dns);
+}
+#if __INCLUDE_LEVEL__ == 0
+int shouldexit = 0;
+void handler (int signal __attribute__((unused))) {
+ shouldexit++;
+}
+int main (int argc, char ** argv) {
+ if (argc != 1+1) {
+ fprintf(stderr, "usage: %s respond.to.ptr.with.this.domain.\n", argv[0]);
+ return 1;
+ }
+ signal(SIGINT, handler);
+ signal(SIGTERM, handler);
+ dns * dns = dns_init();
+ dns_set_domain(dns, argv[1]);
+ while (!shouldexit) {
+ dns_run_once(dns);
+ }
+ dns_free(dns);
+}
+#endif