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