/*
	proxynamed.c
	November 2010
	(C) 2010 Eric Shalov. All Rights Reserved.
	
	A simple DNS server that sends through all received "A?" client
	questions to the system resolver, except for those names specified,
	which it redirects to the specified IP with an "A"-record answer.
	
	Usage: $ proxynamed masquerade-ip [ list of domain-names ]
	
	i.e.
	
		$ proxynamed 192.168.0.1 www.ibm.com www.sun.com

		..will pass through all DNS A requests except those for IBM
		  and Sun, which it will respond to with 192.168.0.1.
	
	Tested on MacOS/X 10.5.8

	Reference:	
	http://tools.ietf.org/html/rfc1035
*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>

#define BUFLEN 512 /* UDP message limit per RFC 1035 */
#define NPACK 10
#define PORT 53

/* A few globals */
FILE *server_log;
char log_entry[128];
int keep_running = 1;
time_t now;

/* prototypes */
void diep(char *s);
void stop_running(int sig);

int main(int argc, char *argv[]) {
	struct sockaddr_in si_me, si_other, client;
	int s;
	socklen_t slen=sizeof(si_other);
	int opt;

	int bytes_in;
	char inbuf[BUFLEN], outbuf[BUFLEN];
	int n,seg;
	unsigned int id;
	int p;
	unsigned long answer_ip_word, masquerade_ip_word;
	char question[128];
	struct hostent *answer_host_ent;
	
	unsigned char opcode, qr;
	unsigned long ttl = 1;
	unsigned short qdcount,ancount,nscount,arcount;
	
	int matches;
	
	pid_t pid;
	
	char *masquerade_ip;
	struct in_addr masquerade_ip_in_addr;
	
	
	if(argc>2) {
		masquerade_ip = argv[1];
	} else {
		fprintf(stderr,"Usage: %s [our ip to masquerade as] [ list of names to masquerade for ]\n", argv[0]);
		exit(1);
	}

	if( ! (server_log=fopen("/var/log/proxynamed.log","a")) ) {
		fprintf(stderr,"Error opening server log file.\n");
		fclose(server_log);
		exit(1);
	}

	if ((s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1)
		diep("socket");

	memset((char *) &si_me, 0, sizeof(si_me));
	si_me.sin_family = AF_INET;
	si_me.sin_port = htons(PORT);
	si_me.sin_addr.s_addr = htonl(INADDR_ANY);
	if (bind(s, (const struct sockaddr *)&si_me, sizeof(si_me))==-1)
		diep("bind");

	if( inet_aton(masquerade_ip, &masquerade_ip_in_addr) == 1 ) {
		masquerade_ip_word = ntohl(masquerade_ip_in_addr.s_addr);
	} else {
		fprintf(stderr,"Error in IP address \"%s\"\n", masquerade_ip);
		exit(1);
	}

	opt = 4;
	setsockopt(s, SOL_SOCKET, SO_RCVLOWAT, &opt, sizeof(int));

	
	/* Daemonize */
	chdir("/");
	pid = fork();
	if(pid != 0 ) {
		printf("proxynamed launched as pid %d\n", pid);

		/* Write log file entry */
  		time(&now);
 		ctime_r(&now,log_entry);
  		sprintf(log_entry+strlen(log_entry)-1, " proxynamed launched as pid %d, responding with %s", pid, masquerade_ip);
		fprintf(server_log,"%s\n", log_entry);
		fflush(server_log);
		
		exit(0); /* The parent can terminate now */
	}
	close(0);
	close(1);
	close(2);
	setpgrp(); /* Disconnect from the controlling terminal */

	signal(SIGTERM, stop_running);
     

	/* main loop */
	while(keep_running) {
		if((bytes_in += recvfrom(s, inbuf, BUFLEN, 0, (struct sockaddr *)&si_other, &slen))==-1)
			diep("recvfrom()");

		memcpy(&client, &si_other, sizeof(struct sockaddr_in));

		id = (unsigned char)inbuf[0] << 8 | (unsigned char)inbuf[1];
		qr = inbuf[2] >> 7;

		/* opcodes: 0=QUERY, 1=IQUERY, 2=STATUS */		
		opcode = (inbuf[2] > 3) & 0x0F;


		/* check that it's a normal query (QR = 0) */
		if( qr == 0 && opcode == 0) {
			*question = '\0';
	   		for(seg=12;inbuf[seg];seg+=inbuf[seg]+1) {
  				for(n=1;n<=inbuf[seg];n++) sprintf(question+strlen(question),"%c", inbuf[seg+n]);
  				if(inbuf[seg+inbuf[seg]+1]) sprintf(question+strlen(question),".");
	 	  	}
	 	  	
			/* check if the requested name matches one on the list */
			matches = 0;
			for(n=2;n<argc && !matches;n++) if(strcmp(argv[n],question)==0) matches = 1;
			
			/* if it matches, use the specified IP, otherwise look it up */
			if(matches) {
				answer_ip_word = masquerade_ip_word;
			} else {
	 			answer_host_ent = gethostbyname(question);
				answer_ip_word = ntohl(*(unsigned long *)answer_host_ent->h_addr_list[0]);
			}

	  		time(&now);
  			ctime_r(&now,log_entry);
  			sprintf(log_entry+strlen(log_entry)-1, " client %s request # %04x for %s",
	  			inet_ntoa(client.sin_addr),
  				(unsigned short)id,
  				question);
  			
	  		fprintf(server_log,"%s\n", log_entry);
	  		fflush(server_log);
  			
  			/* Respond with the client's question and one answer (RR) */
  			qdcount = 1;
  			ancount = 1;
  			nscount = 0;
  			arcount = 0;

	  		p=0;
	  		
	  		/* ID */
  			outbuf[p++] = id >> 8;
  			outbuf[p++] = id & 0xFF;
  			
  			/* QR, Opcode, AA, TC, RD */
	  		outbuf[p++] = 0x81;
	  		
	  		/* RA, Z, RCODE */
  			outbuf[p++] = 0x80;
  			
  			/* QDCOUNT: (qname+4 byte) entries in the question section */
	  		outbuf[p++] = qdcount >> 8;
  			outbuf[p++] = qdcount & 0xFF;
  			
  			/* ANCOUNT: (name+8+RDATA byte) resource records in the answer section */
	  		outbuf[p++] = ancount >> 8;
  			outbuf[p++] = ancount & 0xFF;
  			
  			/* NSCOUNT: name server resource records in the authority records section */
	  		outbuf[p++] = nscount >> 8;
  			outbuf[p++] = nscount & 0xFF;
  			
  			/* ARCOUNT: resource records in the additional records section */
	  		outbuf[p++] = arcount >> 8;
  			outbuf[p++] = arcount & 0xFF;

  			/***** Question sent back to client: *****/
	  		/* copy requested QNAME, segment-by-segment, from client request */
  			for(seg=12;inbuf[seg];seg+=inbuf[seg]+1) {
  				outbuf[p++] = inbuf[seg]; /* Like a Pascal string */
	  			memcpy(outbuf+p,inbuf+seg+1,inbuf[seg]);
  				p+=inbuf[seg];
  			}
	  		outbuf[p++] = 0x00; /* NULL marks end of name */

	  		/* QTYPE (A=0001, NS=0002, 5=CNAME, 6=SOA, 15=MX) */
  			outbuf[p++] = 0x00;
  			outbuf[p++] = 0x01;
  		
	  		/* QCLASS (Internet = 0001) */
  			outbuf[p++] = 0x00;
	  		outbuf[p++] = 0x01;
  		
  		
  			/***** Answer (RR): *****/
  			/* Question QNAME (DNS-compressed): 0xc00c0001 */
  			outbuf[p++] = 0xc0; /* 11000000 two high bits indicate a pointer */
  			outbuf[p++] = 0x0c; /* rest of bits indicate offset, name already sent in query begins 12 bytes into packet */
  			
	  		/* QTYPE (A=0001, NS=0002)*/
  			outbuf[p++] = 0x00;
  			outbuf[p++] = 0x01;
  			
	  		/* QCLASS (Internet = 0001) */
	  		outbuf[p++] = 0x00;
  			outbuf[p++] = 0x01;

			/* TTL: 0x0003f480 = 259200 seconds = 3 days */
	  		outbuf[p++] = (ttl >> 24) & 0xFF;
  			outbuf[p++] = (ttl >> 16) & 0xFF;
  			outbuf[p++] = (ttl >> 8) & 0xFF;
	  		outbuf[p++] = ttl & 0xFF;
      
      			/* RDLENGTH: one IPv4 address = 4 bytes */
	  		outbuf[p++] = 0x00;
  			outbuf[p++] = 0x04;
  		
  			/* RDATA: the IPv4 address */
	  		outbuf[p++] = (answer_ip_word >> 24) & 0xFF;
  			outbuf[p++] = (answer_ip_word >> 16) & 0xFF;
  			outbuf[p++] = (answer_ip_word >> 8) & 0xFF;
	  		outbuf[p++] = answer_ip_word & 0xFF;

	 		sendto(s, outbuf, p, 0, (struct sockaddr *)&client, slen);
	 	} else {
			printf("%d byte packet arrived: qr=%d, opcode=%d\n", bytes_in, qr,opcode);
	 	}
	}

	close(s);
	fclose(server_log);
	return 0;
}

void diep(char *s) {
	perror(s);
	exit(1);
}

void stop_running(int sig) {
	time(&now);
 	ctime_r(&now,log_entry);
  	sprintf(log_entry+strlen(log_entry)-1, " proxynamed (pid %d) received signal %d, shutting down", getpid(), sig);
	fprintf(server_log,"%s\n", log_entry);
	fclose(server_log);

	keep_running = 0;

	exit(0);
}

