xref: /haiku/src/preferences/mail/DNSQuery.cpp (revision 03187b607b2b5eec7ee059f1ead09bdba14991fb)
1 #include "DNSQuery.h"
2 
3 #include <ByteOrder.h>
4 #include <NetAddress.h>
5 #include <NetEndpoint.h>
6 
7 #include <errno.h>
8 #include <stdio.h>
9 
10 // #define DEBUG 1
11 
12 #undef PRINT
13 #ifdef DEBUG
14 #define PRINT(a...) printf(a)
15 #else
16 #define PRINT(a...)
17 #endif
18 
19 static vint32 gID = 1;
20 
21 
22 BRawNetBuffer::BRawNetBuffer()
23 {
24 	_Init(NULL, 0);
25 }
26 
27 
28 BRawNetBuffer::BRawNetBuffer(off_t size)
29 {
30 	_Init(NULL, 0);
31 	fBuffer.SetSize(size);
32 }
33 
34 
35 BRawNetBuffer::BRawNetBuffer(const void* buf, size_t size)
36 {
37 	_Init(buf, size);
38 }
39 
40 
41 status_t
42 BRawNetBuffer::AppendUint16(uint16 value)
43 {
44 	uint16 netVal = B_HOST_TO_BENDIAN_INT16(value);
45 	ssize_t sizeW = fBuffer.WriteAt(fWritePosition, &netVal, sizeof(uint16));
46 	if (sizeW == B_NO_MEMORY)
47 		return B_NO_MEMORY;
48 	fWritePosition += sizeof(uint16);
49 	return B_OK;
50 }
51 
52 
53 status_t
54 BRawNetBuffer::AppendString(const char* string)
55 {
56 	size_t length = strlen(string) + 1;
57 	ssize_t sizeW = fBuffer.WriteAt(fWritePosition, string, length);
58 	if (sizeW == B_NO_MEMORY)
59 		return B_NO_MEMORY;
60 	fWritePosition += length;
61 	return B_OK;
62 }
63 
64 
65 status_t
66 BRawNetBuffer::ReadUint16(uint16& value)
67 {
68 	uint16 netVal;
69 	ssize_t sizeW = fBuffer.ReadAt(fReadPosition, &netVal, sizeof(uint16));
70 	if (sizeW == 0)
71 		return B_ERROR;
72 	value= B_BENDIAN_TO_HOST_INT16(netVal);
73 	fReadPosition += sizeof(uint16);
74 	return B_OK;
75 }
76 
77 
78 status_t
79 BRawNetBuffer::ReadUint32(uint32& value)
80 {
81 	uint32 netVal;
82 	ssize_t sizeW = fBuffer.ReadAt(fReadPosition, &netVal, sizeof(uint32));
83 	if (sizeW == 0)
84 		return B_ERROR;
85 	value= B_BENDIAN_TO_HOST_INT32(netVal);
86 	fReadPosition += sizeof(uint32);
87 	return B_OK;
88 }
89 
90 
91 status_t
92 BRawNetBuffer::ReadString(BString& string)
93 {
94 	char* buffer = (char*)fBuffer.Buffer();
95 	buffer = &buffer[fReadPosition];
96 
97 	// if the string is compressed we have to follow the links to the
98 	// sub strings
99 	while (*buffer != 0) {
100 		if (uint8(*buffer) == 192) {
101 			// found a pointer mark
102 			buffer++;
103 			// pointer takes 2 byte
104 			fReadPosition = fReadPosition + 1;
105 			off_t pos = uint8(*buffer);
106 			_ReadSubString(string, pos);
107 			break;
108 		}
109 		string.Append(buffer, 1);
110 		buffer++;
111 		fReadPosition++;
112 	}
113 	fReadPosition++;
114 	return B_OK;
115 }
116 
117 
118 status_t
119 BRawNetBuffer::SkipReading(off_t skip)
120 {
121 	if (fReadPosition + skip > fBuffer.BufferLength())
122 		return B_ERROR;
123 	fReadPosition += skip;
124 	return B_OK;
125 }
126 
127 
128 void
129 BRawNetBuffer::_Init(const void* buf, size_t size)
130 {
131 	fWritePosition = 0;
132 	fReadPosition = 0;
133 	fBuffer.WriteAt(fWritePosition, buf, size);
134 }
135 
136 
137 void
138 BRawNetBuffer::_ReadSubString(BString& string, off_t pos)
139 {
140 	// sub strings have no links to other substrings so we can read it in one
141 	// piece
142 	char* buffer = (char*)fBuffer.Buffer();
143 	string.Append(&buffer[pos]);
144 }
145 
146 
147 // #pragma mark - DNSTools
148 
149 
150 void
151 DNSTools::GetDNSServers(BObjectList<BString>* serverList)
152 {
153 #define	MATCH(line, name) \
154 	(!strncmp(line, name, sizeof(name) - 1) && \
155 	(line[sizeof(name) - 1] == ' ' || \
156 	 line[sizeof(name) - 1] == '\t'))
157 
158 
159 	register FILE* fp = fopen("/etc/resolv.conf", "r");
160 	if (fp == NULL) {
161 		fprintf(stderr, "failed to open '/etc/resolv.conf' to "
162 			"read nameservers: %s\n", strerror(errno));
163 		return;
164 	}
165 
166 	int nserv = 0;
167 	char buf[1024];
168 	register char *cp; //, **pp;
169 //	register int n;
170 	int MAXNS = 2;
171 
172 	// read the config file
173 	while (fgets(buf, sizeof(buf), fp) != NULL) {
174 		// skip comments
175 		if (*buf == ';' || *buf == '#')
176 			continue;
177 
178 		// read nameservers to query
179 		if (MATCH(buf, "nameserver") && nserv < MAXNS) {
180 //			char sbuf[2];
181 			cp = buf + sizeof("nameserver") - 1;
182 			while (*cp == ' ' || *cp == '\t')
183 				cp++;
184 			cp[strcspn(cp, ";# \t\n")] = '\0';
185 			if ((*cp != '\0') && (*cp != '\n')) {
186 				serverList->AddItem(new BString(cp));
187 				nserv++;
188 			}
189 		}
190 		continue;
191 	}
192 
193 	fclose(fp);
194 }
195 
196 
197 BString
198 DNSTools::ConvertToDNSName(const BString& string)
199 {
200 	BString outString = string;
201 	int32 dot, lastDot, diff;
202 
203 	dot = string.FindFirst(".");
204 	if (dot != B_ERROR) {
205 		outString.Prepend((char*)&dot, 1);
206 		// because we prepend a char add 1 more
207 		lastDot = dot + 1;
208 
209 		while (true) {
210 			dot = outString.FindFirst(".", lastDot + 1);
211 			if (dot == B_ERROR)
212 				break;
213 
214 			// set a counts to the dot
215 			diff =  dot - 1 - lastDot;
216 			outString[lastDot] = (char)diff;
217 			lastDot = dot;
218 		}
219 	} else
220 		lastDot = 0;
221 
222 	diff = outString.CountChars() - 1 - lastDot;
223 	outString[lastDot] = (char)diff;
224 
225 	return outString;
226 }
227 
228 
229 BString
230 DNSTools::ConvertFromDNSName(const BString& string)
231 {
232 	BString outString = string;
233 	int32 dot = string[0];
234 	int32 nextDot = dot;
235 	outString.Remove(0, sizeof(char));
236 	while (true) {
237 		dot = outString[nextDot];
238 		if (dot == 0)
239 			break;
240 		// set a "."
241 		outString[nextDot] = '.';
242 		nextDot+= dot + 1;
243 	}
244 	return outString;
245 }
246 
247 
248 // #pragma mark - DNSQuery
249 // see http://tools.ietf.org/html/rfc1035 for more information about DNS
250 
251 
252 DNSQuery::DNSQuery()
253 {
254 }
255 
256 
257 DNSQuery::~DNSQuery()
258 {
259 }
260 
261 
262 status_t
263 DNSQuery::ReadDNSServer(in_addr* add)
264 {
265 	// list owns the items
266 	BObjectList<BString> dnsServerList(5, true);
267 	DNSTools::GetDNSServers(&dnsServerList);
268 	BString* firstDNS = dnsServerList.ItemAt(0);
269 	int status = -1;
270 	if (firstDNS)
271 		status = inet_aton(firstDNS->String(), add);
272 	else
273 		return B_ERROR;
274 
275 	if (status != 1)
276 		return B_ERROR;
277 	PRINT("dns server found: %s \n", firstDNS->String());
278 	return B_OK;
279 }
280 
281 
282 status_t
283 DNSQuery::GetMXRecords(BString serverName, BObjectList<mx_record>* mxList,
284 	bigtime_t timeout)
285 {
286 	// get the DNS server to ask for the mx record
287 	in_addr dnsAddress;
288 	if (ReadDNSServer(&dnsAddress) != B_OK)
289 		return B_ERROR;
290 
291 	// create dns query package
292 	BRawNetBuffer buffer;
293 	dns_header header;
294 	_SetMXHeader(&header);
295 	_AppendQueryHeader(buffer, &header);
296 
297 	BString serverNameConv = DNSTools::ConvertToDNSName(serverName);
298 	buffer.AppendString(serverNameConv.String());
299 	buffer.AppendUint16(uint16(MX_RECORD));
300 	buffer.AppendUint16(uint16(1));
301 
302 	// send the buffer
303 	PRINT("send buffer\n");
304 	BNetAddress netAddress(dnsAddress, 53);
305 	BNetEndpoint netEndpoint(SOCK_DGRAM);
306 	if (netEndpoint.InitCheck() != B_OK)
307 		return B_ERROR;
308 
309 	if (netEndpoint.Connect(netAddress) != B_OK)
310 		return B_ERROR;
311 	PRINT("Connected\n");
312 
313 #ifdef DEBUG
314 	int32 bytesSend =
315 #endif
316 	netEndpoint.Send(buffer.Data(), buffer.Size());
317 	PRINT("bytes send %i\n", int(bytesSend));
318 
319 	// receive buffer
320 	BRawNetBuffer receiBuffer(512);
321 	netEndpoint.SetTimeout(timeout);
322 #ifdef DEBUG
323 	int32 bytesRecei =
324 #endif
325 	netEndpoint.ReceiveFrom(receiBuffer.Data(), 512, netAddress);
326 	PRINT("bytes received %i\n", int(bytesRecei));
327 	dns_header receiHeader;
328 
329 	_ReadQueryHeader(receiBuffer, &receiHeader);
330 	PRINT("Package contains :");
331 	PRINT("%d Questions, ", receiHeader.q_count);
332 	PRINT("%d Answers, ", receiHeader.ans_count);
333 	PRINT("%d Authoritative Servers, ", receiHeader.auth_count);
334 	PRINT("%d Additional records\n", receiHeader.add_count);
335 
336 	// remove name and Question
337 	BString dummyS;
338 	uint16 dummy;
339 	receiBuffer.ReadString(dummyS);
340 	receiBuffer.ReadUint16(dummy);
341 	receiBuffer.ReadUint16(dummy);
342 
343 	bool mxRecordFound = false;
344 	for (int i = 0; i < receiHeader.ans_count; i++) {
345 		resource_record_head rrHead;
346 		_ReadResourceRecord(receiBuffer, &rrHead);
347 		if (rrHead.type == MX_RECORD) {
348 			mx_record *mxRec = new mx_record;
349 			_ReadMXRecord(receiBuffer, mxRec);
350 			PRINT("MX record found pri %i, name %s\n",
351 					mxRec->priority,
352 					mxRec->serverName.String());
353 			// Add mx record to the list
354 			mxList->AddItem(mxRec);
355 			mxRecordFound = true;
356 		} else {
357 			buffer.SkipReading(rrHead.dataLength);
358 		}
359 	}
360 
361 	if (!mxRecordFound)
362 		return B_ERROR;
363 
364 	return B_OK;
365 }
366 
367 
368 uint16
369 DNSQuery::_GetUniqueID()
370 {
371 	int32 nextId= atomic_add(&gID, 1);
372 	// just to be sure
373 	if (nextId > 65529)
374 		nextId = 0;
375 	return nextId;
376 }
377 
378 
379 void
380 DNSQuery::_SetMXHeader(dns_header* header)
381 {
382 	header->id = _GetUniqueID();
383 	header->qr = 0;      //This is a query
384 	header->opcode = 0;  //This is a standard query
385 	header->aa = 0;      //Not Authoritative
386 	header->tc = 0;      //This message is not truncated
387 	header->rd = 1;      //Recursion Desired
388 	header->ra = 0;      //Recursion not available! hey we dont have it (lol)
389 	header->z  = 0;
390 	header->rcode = 0;
391 	header->q_count = 1;   //we have only 1 question
392 	header->ans_count  = 0;
393 	header->auth_count = 0;
394 	header->add_count  = 0;
395 }
396 
397 
398 void
399 DNSQuery::_AppendQueryHeader(BRawNetBuffer& buffer, const dns_header* header)
400 {
401 	buffer.AppendUint16(header->id);
402 	uint16 data = 0;
403 	data |= header->rcode;
404 	data |= header->z << 4;
405 	data |= header->ra << 7;
406 	data |= header->rd << 8;
407 	data |= header->tc << 9;
408 	data |= header->aa << 10;
409 	data |= header->opcode << 11;
410 	data |= header->qr << 15;
411 	buffer.AppendUint16(data);
412 	buffer.AppendUint16(header->q_count);
413 	buffer.AppendUint16(header->ans_count);
414 	buffer.AppendUint16(header->auth_count);
415 	buffer.AppendUint16(header->add_count);
416 }
417 
418 
419 void
420 DNSQuery::_ReadQueryHeader(BRawNetBuffer& buffer, dns_header* header)
421 {
422 	buffer.ReadUint16(header->id);
423 	uint16 data = 0;
424 	buffer.ReadUint16(data);
425 	header->rcode = data & 0x0F;
426 	header->z = (data >> 4) & 0x07;
427 	header->ra = (data >> 7) & 0x01;
428 	header->rd = (data >> 8) & 0x01;
429 	header->tc = (data >> 9) & 0x01;
430 	header->aa = (data >> 10) & 0x01;
431 	header->opcode = (data >> 11) & 0x0F;
432 	header->qr = (data >> 15) & 0x01;
433 	buffer.ReadUint16(header->q_count);
434 	buffer.ReadUint16(header->ans_count);
435 	buffer.ReadUint16(header->auth_count);
436 	buffer.ReadUint16(header->add_count);
437 }
438 
439 
440 void
441 DNSQuery::_ReadMXRecord(BRawNetBuffer& buffer, mx_record* mxRecord)
442 {
443 	buffer.ReadUint16(mxRecord->priority);
444 	buffer.ReadString(mxRecord->serverName);
445 	mxRecord->serverName = DNSTools::ConvertFromDNSName(mxRecord->serverName);
446 }
447 
448 
449 void
450 DNSQuery::_ReadResourceRecord(BRawNetBuffer& buffer,
451 	resource_record_head *rrHead)
452 {
453 	buffer.ReadString(rrHead->name);
454 	buffer.ReadUint16(rrHead->type);
455 	buffer.ReadUint16(rrHead->dataClass);
456 	buffer.ReadUint32(rrHead->ttl);
457 	buffer.ReadUint16(rrHead->dataLength);
458 }
459