summaryrefslogtreecommitdiff
path: root/src/io/config.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/io/config.c')
-rw-r--r--src/io/config.c476
1 files changed, 476 insertions, 0 deletions
diff --git a/src/io/config.c b/src/io/config.c
new file mode 100644
index 0000000..7bd4522
--- /dev/null
+++ b/src/io/config.c
@@ -0,0 +1,476 @@
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "config.h"
+#include "log.h"
+#include "map.h"
+
+#define MAX_LEN 1024
+#define BUF(name) char name[MAX_LEN]
+
+static int line = 0;
+
+static bool get_line(FILE* file, const BUF(buf)) {
+ line++;
+ return fgets((char*) buf, MAX_LEN, file) != NULL;
+}
+
+static bool is_whitespace(const char* buf) {
+ int i = 0;
+ char c;
+ while(c = buf[i], 1) {
+ if (c == '\n' || c == '\0') return true;
+ if (c != ' ' && c != '\n') return false;
+ i++;
+ }
+}
+
+static bool get_words(char* buf, char** words, int count) {
+ int last = 0;
+ int offset = 0;
+ int i = 0;
+
+ for(i = 0; i < count; i++) {
+ char c;
+ while(c = buf[offset], c != ' ' && c != '\0' && c != '\n') {
+ offset++;
+ }
+
+ if (offset - last < 1) {
+ return false;
+ }
+ words[i] = buf + last;
+ buf[offset] = '\0';
+ offset++;
+ last = offset;
+
+ if (c == '\0' || c == '\n') {
+ break;
+ }
+
+ }
+ return i + 1 == count;
+}
+
+static bool get_int(const char* word, uint32_t* i) {
+ char* end;
+ uint32_t res = (uint32_t) strtol(word, &end, 10);
+
+ if (*end == '\0') {
+ *i = res;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool config_read_qtype(const char* qstr, RecordType* qtype) {
+ if (strcmp(qstr, "A") == 0) {
+ *qtype = A;
+ return true;
+ } else if (strcmp(qstr, "NS") == 0) {
+ *qtype = NS;
+ return true;
+ } else if (strcmp(qstr, "CNAME") == 0) {
+ *qtype = CNAME;
+ return true;
+ } else if (strcmp(qstr, "SOA") == 0) {
+ *qtype = SOA;
+ return true;
+ } else if (strcmp(qstr, "PTR") == 0) {
+ *qtype = PTR;
+ return true;
+ } else if (strcmp(qstr, "MX") == 0) {
+ *qtype = MX;
+ return true;
+ } else if (strcmp(qstr, "TXT") == 0) {
+ *qtype = TXT;
+ return true;
+ } else if (strcmp(qstr, "AAAA") == 0) {
+ *qtype = AAAA;
+ return true;
+ } else if (strcmp(qstr, "SRV") == 0) {
+ *qtype = SRV;
+ return true;
+ } else if (strcmp(qstr, "CAA") == 0) {
+ *qtype = CAA;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool config_read_class(const char* cstr, uint16_t* class) {
+ if (strcmp(cstr, "IN") == 0) {
+ *class = 1;
+ return true;
+ } else if (strcmp(cstr, "CH") == 0) {
+ *class = 3;
+ return true;
+ } else if (strcmp(cstr, "HS") == 0) {
+ *class = 4;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// Format QTYPE CLASS DOMAIN: A IN google.com
+static bool config_read_question(FILE* file, Question* question) {
+ BUF(buf);
+ if (!get_line(file, buf)) {
+ return false;
+ }
+
+ if (is_whitespace(buf)) {
+ return false;
+ }
+
+ char* words[3];
+ if (!get_words(&buf[0], &words[0], 3)) {
+ WARN("Invalid question at line %d", line);
+ return false;
+ };
+
+ uint16_t class;
+ if (!config_read_class(words[0], &class)) {
+ WARN("Invalid question class at line %d", line);
+ return false;
+ }
+
+ RecordType qtype;
+ if (!config_read_qtype(words[1], &qtype)) {
+ WARN("Invalid question qtype at line %d", line);
+ return false;
+ }
+
+ size_t domain_len = strlen(words[2]);
+ question->cls = class;
+ question->qtype = qtype;
+ question->domain = malloc(domain_len + 1);
+ question->domain[0] = domain_len;
+ memcpy(question->domain + 1, words[2], domain_len);
+
+ return true;
+}
+
+static void copy_str(char* from, uint8_t** to) {
+ size_t len = strlen(from);
+ if (len > 255) {
+ len = 255;
+ }
+
+ uint8_t* new = malloc(len + 1);
+ new[0] = len;
+ memcpy(new + 1, from, len);
+ *to = new;
+}
+
+static bool config_read_a_record(char* data, ARecord* record) {
+ sscanf(data, "%hhu.%hhu.%hhu.%hhu",
+ &record->addr[0],
+ &record->addr[1],
+ &record->addr[2],
+ &record->addr[3]
+ );
+ return true;
+}
+
+static bool config_read_ns_record(char* data, NSRecord* record) {
+ copy_str(data, &record->host);
+ return true;
+}
+
+static bool config_read_cname_record(char* data, CNAMERecord* record) {
+ copy_str(data, &record->host);
+ return true;
+}
+
+static bool config_read_soa_record(char* data, SOARecord* record) {
+ char* words[7];
+ if (!get_words(&data[0], &words[0], 7)) {
+ WARN("Invalid SOA record data at line %d", line);
+ record->mname = NULL;
+ record->nname = NULL;
+ return false;
+ }
+
+ copy_str(words[0], &record->mname);
+ copy_str(words[1], &record->nname);
+
+ if (!get_int(words[2], &record->serial)) {
+ WARN("Invalid SOA record data at line %d", line);
+ return false;
+ }
+
+ if (!get_int(words[3], &record->refresh)) {
+ WARN("Invalid SOA record data at line %d", line);
+ return false;
+ }
+
+ if (!get_int(words[4], &record->retry)) {
+ WARN("Invalid SOA record data at line %d", line);
+ return false;
+ }
+
+ if (!get_int(words[5], &record->expire)) {
+ WARN("Invalid SOA record data at line %d", line);
+ return false;
+ }
+
+ if (!get_int(words[6], &record->minimum)) {
+ WARN("Invalid SOA record data at line %d", line);
+ return false;
+ }
+
+ return true;
+}
+
+static bool config_read_ptr_record(char* data, PTRRecord* record) {
+ copy_str(data, &record->pointer);
+ return true;
+}
+
+static bool config_read_mx_record(char* data, MXRecord* record) {
+ char* words[2];
+ if (!get_words(&data[0], &words[0], 2)) {
+ WARN("Invalid MX record data at line %d", line);
+ record->host = NULL;
+ return false;
+ }
+
+ copy_str(words[1], &record->host);
+
+ uint32_t priority;
+ if (!get_int(words[0], &priority)) {
+ WARN("Invalid MX record data at line %d", line);
+ return false;
+ }
+ record->priority = (uint16_t) priority;
+
+ return true;
+}
+
+static bool config_read_txt_record(char* data, TXTRecord* record) {
+ int len = strlen(data);
+ uint8_t count = ((uint8_t)len + 254) / 255;
+ record->len = count;
+ record->text = malloc(sizeof(uint8_t*) * count);
+
+ for (uint8_t i = 0; i < count; i++) {
+ uint32_t offset = count * 255;
+ uint32_t part_len = len - offset;
+ if (part_len > 255) part_len = 255;
+
+ uint8_t* part = malloc(part_len + 1);
+ part[0] = part_len;
+ memcpy(part + 1, data + offset, part_len);
+
+ record->text[i] = part;
+ }
+
+ return true;
+}
+
+static bool config_read_aaaa_record(char* data, AAAARecord* record) {
+ for(int i = 0; i < 8; i++) {
+ if (sscanf(data, "%02hhx%02hhx:",
+ &record->addr[i*2 + 0],
+ &record->addr[i*2 + 1]
+ ) == EOF) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool config_read_srv_record(char* data, SRVRecord* record) {
+ char* words[4];
+ if (!get_words(&data[0], &words[0], 4)) {
+ WARN("Invalid SRV record data at line %d", line);
+ record->target = NULL;
+ return false;
+ }
+
+ copy_str(words[3], &record->target);
+
+ uint32_t priority;
+ if (!get_int(words[0], &priority)) {
+ WARN("Invalid SRV record data at line %d", line);
+ return false;
+ }
+ record->priority = (uint16_t) priority;
+
+ uint32_t weight;
+ if (!get_int(words[1], &weight)) {
+ WARN("Invalid SRV record data at line %d", line);
+ return false;
+ }
+ record->weight = (uint16_t) weight;
+
+ uint32_t port;
+ if (!get_int(words[2], &port)) {
+ WARN("Invalid SRV record data at line %d", line);
+ return false;
+ }
+ record->port = (uint16_t) port;
+
+ return true;
+}
+
+static bool config_read_caa_record(char* data, CAARecord* record) {
+ char* words[4];
+ if (!get_words(&data[0], &words[0], 4)) {
+ WARN("Invalid SRV record data at line %d", line);
+ record->tag = NULL;
+ record->value = NULL;
+ return false;
+ }
+
+ copy_str(words[2], &record->tag);
+ copy_str(words[3], &record->value);
+
+ uint32_t flags;
+ if (!get_int(words[0], &flags)) {
+ WARN("Invalid SRV record data at line %d", line);
+ return false;
+ }
+ record->flags = (uint8_t) flags;
+
+ uint32_t length;
+ if (!get_int(words[1], &length)) {
+ WARN("Invalid SRV record data at line %d", line);
+ return false;
+ }
+ record->length = (uint8_t) length;
+
+ return true;
+}
+
+static bool config_read_record_data(char* data, Record* record) {
+ switch (record->type) {
+ case UNKOWN:
+ // This can never happend in here so uh do nothing i guess
+ return false;
+ case A:
+ return config_read_a_record(data, &record->data.a);
+ case NS:
+ return config_read_ns_record(data, &record->data.ns);
+ case CNAME:
+ return config_read_cname_record(data, &record->data.cname);
+ case SOA:
+ return config_read_soa_record(data, &record->data.soa);
+ case PTR:
+ return config_read_ptr_record(data, &record->data.ptr);
+ case MX:
+ return config_read_mx_record(data, &record->data.mx);
+ case TXT:
+ return config_read_txt_record(data, &record->data.txt);
+ case AAAA:
+ return config_read_aaaa_record(data, &record->data.aaaa);
+ case SRV:
+ return config_read_srv_record(data, &record->data.srv);
+ case CAA:
+ return config_read_caa_record(data, &record->data.caa);
+ }
+ return false;
+}
+
+static bool config_read_record(FILE* file, Record* record, Question* question) {
+ BUF(buf);
+ if (!get_line(file, buf)) {
+ return false;
+ }
+
+ if (is_whitespace(buf)) {
+ return false;
+ }
+
+ char* words[2];
+ if (!get_words(&buf[0], &words[0], 2)) {
+ WARN("Invalid record at line %d", line);
+ return false;
+ }
+
+ uint32_t ttl;
+ if (!get_int(words[0], &ttl)) {
+ WARN("Invalid record ttl at line %d", line);
+ return false;
+ }
+
+ record->cls = question->cls;
+ record->type = question->qtype;
+ record->len = 0;
+ record->ttl = ttl;
+ record->domain = malloc(question->domain[0] + 1);
+ memcpy(record->domain, question->domain, question->domain[0] + 1);
+
+ if(!config_read_record_data(words[1], record)) {
+ free_record(record);
+ return false;
+ }
+
+ return true;
+}
+
+static void config_push_record(Record** buf, Record record, uint16_t* capacity, uint16_t* size) {
+ if (size == capacity) {
+ *capacity *= 2;
+ *buf = realloc(*buf, sizeof(Record) * *capacity);
+ }
+ (*buf)[*size] = record;
+ (*size)++;
+}
+
+bool load_config(const char* path, RecordMap* map) {
+ FILE* file = fopen(path, "r");
+ if (file == NULL) {
+ ERROR("Failed to open file %s: %s", path, strerror(errno));
+ return false;
+ }
+
+ line = 0;
+ record_map_init(map);
+
+ while (1) {
+ Question* question = malloc(sizeof(Question));
+ if (!config_read_question(file, question)) {
+ free(question);
+ break;
+ }
+
+ INIT_LOG_BUFFER(log);
+ LOGONLY(print_question(question, log));
+ TRACE("Found config question: %s", log);
+
+ Packet* packet = malloc(sizeof(Packet));
+ memset(packet, 0, sizeof(Packet));
+ packet->authorities = NULL;
+ packet->resources = NULL;
+
+ packet->questions = malloc(sizeof(Question));
+ packet->questions[0] = *question;
+
+ uint16_t capacity = 1;
+ packet->answers = malloc(sizeof(Record));
+
+ while(1) {
+ Record record;
+ if (!config_read_record(file, &record, question)) {
+ break;
+ }
+
+ LOGONLY(print_record(&record, log));
+ TRACE("Found config record: %s", log);
+
+ config_push_record(&packet->answers, record, &capacity, &packet->header.answers);
+ }
+
+ record_map_add(map, question, packet);
+ }
+
+ fclose(file);
+ return true;
+}