xref: /PHP-8.4/ext/standard/dns_win32.c (revision 5853cdb7)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Pierre A. Joye <pierre@php.net>                             |
14    +----------------------------------------------------------------------+
15  */
16 
17 #include "php.h"
18 
19 #include <windows.h>
20 #include <Winbase.h >
21 #include <Windns.h>
22 #include <Ws2tcpip.h>
23 
24 #include "php_dns.h"
25 
26 #define PHP_DNS_NUM_TYPES	12	/* Number of DNS Types Supported by PHP currently */
27 
28 #define PHP_DNS_A      0x00000001
29 #define PHP_DNS_NS     0x00000002
30 #define PHP_DNS_CNAME  0x00000010
31 #define PHP_DNS_SOA    0x00000020
32 #define PHP_DNS_PTR    0x00000800
33 #define PHP_DNS_HINFO  0x00001000
34 #define PHP_DNS_MX     0x00004000
35 #define PHP_DNS_TXT    0x00008000
36 #define PHP_DNS_A6     0x01000000
37 #define PHP_DNS_SRV    0x02000000
38 #define PHP_DNS_NAPTR  0x04000000
39 #define PHP_DNS_AAAA   0x08000000
40 #define PHP_DNS_ANY    0x10000000
41 #define PHP_DNS_ALL    (PHP_DNS_A|PHP_DNS_NS|PHP_DNS_CNAME|PHP_DNS_SOA|PHP_DNS_PTR|PHP_DNS_HINFO|PHP_DNS_MX|PHP_DNS_TXT|PHP_DNS_A6|PHP_DNS_SRV|PHP_DNS_NAPTR|PHP_DNS_AAAA)
42 
PHP_FUNCTION(dns_get_mx)43 PHP_FUNCTION(dns_get_mx) /* {{{ */
44 {
45 	char *hostname;
46 	size_t hostname_len;
47 	zval *mx_list, *weight_list = NULL;
48 
49 	DNS_STATUS      status;                 /* Return value of DnsQuery_A() function */
50 	PDNS_RECORD     pResult, pRec;          /* Pointer to DNS_RECORD structure */
51 
52 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz|z", &hostname, &hostname_len, &mx_list, &weight_list) == FAILURE) {
53 		RETURN_THROWS();
54 	}
55 
56 	status = DnsQuery_A(hostname, DNS_TYPE_MX, DNS_QUERY_STANDARD, NULL, &pResult, NULL);
57 
58 	if (status) {
59 		RETURN_FALSE;
60 	}
61 
62 	mx_list = zend_try_array_init(mx_list);
63 	if (!mx_list) {
64 		goto cleanup;
65 	}
66 
67 	if (weight_list) {
68 		weight_list = zend_try_array_init(weight_list);
69 		if (!weight_list) {
70 			goto cleanup;
71 		}
72 	}
73 
74 	for (pRec = pResult; pRec; pRec = pRec->pNext) {
75 		DNS_SRV_DATA *srv = &pRec->Data.Srv;
76 
77 		if (pRec->wType != DNS_TYPE_MX) {
78 			continue;
79 		}
80 
81 		add_next_index_string(mx_list, pRec->Data.MX.pNameExchange);
82 		if (weight_list) {
83 			add_next_index_long(weight_list, srv->wPriority);
84 		}
85 	}
86 
87 cleanup:
88 	/* Free memory allocated for DNS records. */
89 	DnsRecordListFree(pResult, DnsFreeRecordListDeep);
90 
91 	RETURN_TRUE;
92 }
93 /* }}} */
94 
95 /* {{{ Check DNS records corresponding to a given Internet host name or IP address */
PHP_FUNCTION(dns_check_record)96 PHP_FUNCTION(dns_check_record)
97 {
98 	char *hostname;
99 	size_t hostname_len;
100 	zend_string *rectype = NULL;
101 	int type = DNS_TYPE_MX;
102 
103 	DNS_STATUS      status;                 /* Return value of DnsQuery_A() function */
104 	PDNS_RECORD     pResult;          /* Pointer to DNS_RECORD structure */
105 
106 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|S", &hostname, &hostname_len, &rectype) == FAILURE) {
107 		RETURN_THROWS();
108 	}
109 
110 	if (hostname_len == 0) {
111 		zend_argument_must_not_be_empty_error(1);
112 		RETURN_THROWS();
113 	}
114 
115 	if (rectype) {
116 		     if (zend_string_equals_literal_ci(rectype,     "A")) type = DNS_TYPE_A;
117 		else if (zend_string_equals_literal_ci(rectype,    "NS")) type = DNS_TYPE_NS;
118 		else if (zend_string_equals_literal_ci(rectype,    "MX")) type = DNS_TYPE_MX;
119 		else if (zend_string_equals_literal_ci(rectype,   "PTR")) type = DNS_TYPE_PTR;
120 		else if (zend_string_equals_literal_ci(rectype,   "ANY")) type = DNS_TYPE_ANY;
121 		else if (zend_string_equals_literal_ci(rectype,   "SOA")) type = DNS_TYPE_SOA;
122 		else if (zend_string_equals_literal_ci(rectype,   "TXT")) type = DNS_TYPE_TEXT;
123 		else if (zend_string_equals_literal_ci(rectype, "CNAME")) type = DNS_TYPE_CNAME;
124 		else if (zend_string_equals_literal_ci(rectype,  "AAAA")) type = DNS_TYPE_AAAA;
125 		else if (zend_string_equals_literal_ci(rectype,   "SRV")) type = DNS_TYPE_SRV;
126 		else if (zend_string_equals_literal_ci(rectype, "NAPTR")) type = DNS_TYPE_NAPTR;
127 		else if (zend_string_equals_literal_ci(rectype,    "A6")) type = DNS_TYPE_A6;
128 		else {
129 			zend_argument_value_error(2, "must be a valid DNS record type");
130 			RETURN_THROWS();
131 		}
132 	}
133 
134 	status = DnsQuery_A(hostname, type, DNS_QUERY_STANDARD, NULL, &pResult, NULL);
135 
136 	if (status) {
137 		RETURN_FALSE;
138 	}
139 
140 	RETURN_TRUE;
141 }
142 /* }}} */
143 
144 /* {{{ php_parserr */
php_parserr(PDNS_RECORD pRec,int type_to_fetch,int store,bool raw,zval * subarray)145 static void php_parserr(PDNS_RECORD pRec, int type_to_fetch, int store, bool raw, zval *subarray)
146 {
147 	int type;
148 	u_long ttl;
149 
150 	type = pRec->wType;
151 	ttl = pRec->dwTtl;
152 
153 	ZVAL_UNDEF(subarray);
154 
155 	if (type_to_fetch != DNS_TYPE_ANY && type != type_to_fetch) {
156 		return;
157 	}
158 
159 	if (!store) {
160 		return;
161 	}
162 
163 	array_init(subarray);
164 
165 	add_assoc_string(subarray, "host", pRec->pName);
166 	add_assoc_string(subarray, "class", "IN");
167 	add_assoc_long(subarray, "ttl", ttl);
168 
169 	if (raw) {
170 		add_assoc_long(subarray, "type", type);
171 		add_assoc_stringl(subarray, "data", (char*) &pRec->Data, (uint32_t) pRec->wDataLength);
172 		return;
173 	}
174 
175 	switch (type) {
176 		case DNS_TYPE_A: {
177 			IN_ADDR ipaddr;
178 			char ip[INET_ADDRSTRLEN];
179 			ipaddr.S_un.S_addr = (pRec->Data.A.IpAddress);
180 			if (!inet_ntop(AF_INET, &ipaddr, ip, INET_ADDRSTRLEN)) {
181 				ZVAL_UNDEF(subarray);
182 			} else {
183 				add_assoc_string(subarray, "type", "A");
184 				add_assoc_string(subarray, "ip", ip);
185 			}
186 			break;
187 		}
188 
189 		case DNS_TYPE_MX:
190 			add_assoc_string(subarray, "type", "MX");
191 			add_assoc_long(subarray, "pri", pRec->Data.Srv.wPriority);
192 			/* no break; */
193 
194 		case DNS_TYPE_CNAME:
195 			if (type == DNS_TYPE_CNAME) {
196 				add_assoc_string(subarray, "type", "CNAME");
197 			}
198 			/* no break; */
199 
200 		case DNS_TYPE_NS:
201 			if (type == DNS_TYPE_NS) {
202 				add_assoc_string(subarray, "type", "NS");
203 			}
204 			/* no break; */
205 
206 		case DNS_TYPE_PTR:
207 			if (type == DNS_TYPE_PTR) {
208 				add_assoc_string(subarray, "type", "PTR");
209 			}
210 			add_assoc_string(subarray, "target", pRec->Data.MX.pNameExchange);
211 			break;
212 
213 		/* Not available on windows, the query is possible but there is no DNS_HINFO_DATA structure */
214 		case DNS_TYPE_HINFO:
215 		case DNS_TYPE_TEXT:
216 			{
217 				DWORD i = 0;
218 				DNS_TXT_DATA *data_txt = &pRec->Data.TXT;
219 				DWORD count = data_txt->dwStringCount;
220 				zend_string *txt;
221 				char *txt_dst;
222 				size_t txt_len = 0;
223 				zval entries;
224 
225 				add_assoc_string(subarray, "type", "TXT");
226 
227 				array_init(&entries);
228 
229 				for (i = 0; i < count; i++) {
230 					txt_len += strlen(data_txt->pStringArray[i]);
231 				}
232 
233 				txt = zend_string_alloc(txt_len, 0);
234 				txt_dst = ZSTR_VAL(txt);
235 				for (i = 0; i < count; i++) {
236 					size_t len = strlen(data_txt->pStringArray[i]);
237 					memcpy(txt_dst, data_txt->pStringArray[i], len);
238 					add_next_index_stringl(&entries, data_txt->pStringArray[i], len);
239 					txt_dst += len;
240 				}
241 				*txt_dst = '\0';
242 				add_assoc_str(subarray, "txt", txt);
243 				add_assoc_zval(subarray, "entries", &entries);
244 			}
245 			break;
246 
247 		case DNS_TYPE_SOA:
248 			{
249 				DNS_SOA_DATA *data_soa = &pRec->Data.Soa;
250 
251 				add_assoc_string(subarray, "type", "SOA");
252 
253 				add_assoc_string(subarray, "mname", data_soa->pNamePrimaryServer);
254 				add_assoc_string(subarray, "rname", data_soa->pNameAdministrator);
255 				add_assoc_long(subarray, "serial", data_soa->dwSerialNo);
256 				add_assoc_long(subarray, "refresh", data_soa->dwRefresh);
257 				add_assoc_long(subarray, "retry", data_soa->dwRetry);
258 				add_assoc_long(subarray, "expire", data_soa->dwExpire);
259 				add_assoc_long(subarray, "minimum-ttl", data_soa->dwDefaultTtl);
260 			}
261 			break;
262 
263 		case DNS_TYPE_AAAA:
264 			{
265 				DNS_AAAA_DATA *data_aaaa = &pRec->Data.AAAA;
266 				char buf[sizeof("AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA:AAAA")];
267 				char *tp = buf;
268 				int i;
269 				unsigned short out[8];
270 				int have_v6_break = 0, in_v6_break = 0;
271 
272 				for (i = 0; i < 4; ++i) {
273 					DWORD chunk = data_aaaa->Ip6Address.IP6Dword[i];
274 					out[i * 2]     = htons(LOWORD(chunk));
275 					out[i * 2 + 1] = htons(HIWORD(chunk));
276 				}
277 
278 				for(i=0; i < 8; i++) {
279 					if (out[i] != 0) {
280 						if (tp > (uint8_t *)buf) {
281 							in_v6_break = 0;
282 							tp[0] = ':';
283 							tp++;
284 						}
285 						tp += snprintf((char*)tp, sizeof(buf) - (tp - (char *) buf), "%x", out[i]);
286 					} else {
287 						if (!have_v6_break) {
288 							have_v6_break = 1;
289 							in_v6_break = 1;
290 							tp[0] = ':';
291 							tp++;
292 						} else if (!in_v6_break) {
293 							tp[0] = ':';
294 							tp++;
295 							tp[0] = '0';
296 							tp++;
297 						}
298 					}
299 				}
300 
301 				if (have_v6_break && in_v6_break) {
302 					tp[0] = ':';
303 					tp++;
304 				}
305 				tp[0] = '\0';
306 
307 				add_assoc_string(subarray, "type", "AAAA");
308 				add_assoc_string(subarray, "ipv6", buf);
309 			}
310 			break;
311 
312 #if 0
313 		/* Won't be implemented. A6 is deprecated. (Pierre) */
314 		case DNS_TYPE_A6:
315 			break;
316 #endif
317 
318 		case DNS_TYPE_SRV:
319 			{
320 				DNS_SRV_DATA *data_srv = &pRec->Data.Srv;
321 
322 				add_assoc_string(subarray, "type", "SRV");
323 				add_assoc_long(subarray, "pri", data_srv->wPriority);
324 				add_assoc_long(subarray, "weight", data_srv->wWeight);
325 				add_assoc_long(subarray, "port", data_srv->wPort);
326 				add_assoc_string(subarray, "target", data_srv->pNameTarget);
327 			}
328 			break;
329 
330 		case DNS_TYPE_NAPTR:
331 			{
332 				DNS_NAPTR_DATA * data_naptr = &pRec->Data.Naptr;
333 
334 				add_assoc_string(subarray, "type", "NAPTR");
335 				add_assoc_long(subarray, "order", data_naptr->wOrder);
336 				add_assoc_long(subarray, "pref", data_naptr->wPreference);
337 				add_assoc_string(subarray, "flags", data_naptr->pFlags);
338 				add_assoc_string(subarray, "services", data_naptr->pService);
339 				add_assoc_string(subarray, "regex", data_naptr->pRegularExpression);
340 				add_assoc_string(subarray, "replacement", data_naptr->pReplacement);
341 			}
342 			break;
343 
344 		default:
345 			/* unknown type */
346 			ZVAL_UNDEF(subarray);
347 			return;
348 	}
349 
350 }
351 /* }}} */
352 
353 /* {{{ Get any Resource Record corresponding to a given Internet host name */
PHP_FUNCTION(dns_get_record)354 PHP_FUNCTION(dns_get_record)
355 {
356 	char *hostname;
357 	size_t hostname_len;
358 	zend_long type_param = PHP_DNS_ANY;
359 	zval *authns = NULL, *addtl = NULL;
360 	int type, type_to_fetch, first_query = 1, store_results = 1;
361 	bool raw = 0;
362 
363 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|lz!z!b",
364 			&hostname, &hostname_len, &type_param, &authns, &addtl, &raw) == FAILURE) {
365 		RETURN_THROWS();
366 	}
367 
368 	if (authns) {
369 		authns = zend_try_array_init(authns);
370 		if (!authns) {
371 			RETURN_THROWS();
372 		}
373 	}
374 	if (addtl) {
375 		addtl = zend_try_array_init(addtl);
376 		if (!addtl) {
377 			RETURN_THROWS();
378 		}
379 	}
380 
381 	if (!raw) {
382 		if ((type_param & ~PHP_DNS_ALL) && (type_param != PHP_DNS_ANY)) {
383 			zend_argument_value_error(2, "must be a DNS_* constant");
384 			RETURN_THROWS();
385 		}
386 	} else {
387 		if ((type_param < 1) || (type_param > 0xFFFF)) {
388 			zend_argument_value_error(2, "must be between 1 and 65535 when argument #5 ($raw) is true");
389 			RETURN_THROWS();
390 		}
391 	}
392 
393 	/* Initialize the return array */
394 	array_init(return_value);
395 
396 	if (raw) {
397 		type = -1;
398 	} else if (type_param == PHP_DNS_ANY) {
399 		type = PHP_DNS_NUM_TYPES + 1;
400 	} else {
401 		type = 0;
402 	}
403 
404 	for ( ;
405 		type < (addtl ? (PHP_DNS_NUM_TYPES + 2) : PHP_DNS_NUM_TYPES) || first_query;
406 		type++
407 	) {
408 		DNS_STATUS      status;                 /* Return value of DnsQuery_A() function */
409 		PDNS_RECORD     pResult, pRec;          /* Pointer to DNS_RECORD structure */
410 
411 		first_query = 0;
412 		switch (type) {
413 			case -1: /* raw */
414 				type_to_fetch = type_param;
415 				/* skip over the rest and go directly to additional records */
416 				type = PHP_DNS_NUM_TYPES - 1;
417 				break;
418 			case 0:
419 				type_to_fetch = type_param&PHP_DNS_A     ? DNS_TYPE_A     : 0;
420 				break;
421 			case 1:
422 				type_to_fetch = type_param&PHP_DNS_NS    ? DNS_TYPE_NS    : 0;
423 				break;
424 			case 2:
425 				type_to_fetch = type_param&PHP_DNS_CNAME ? DNS_TYPE_CNAME : 0;
426 				break;
427 			case 3:
428 				type_to_fetch = type_param&PHP_DNS_SOA   ? DNS_TYPE_SOA   : 0;
429 				break;
430 			case 4:
431 				type_to_fetch = type_param&PHP_DNS_PTR   ? DNS_TYPE_PTR   : 0;
432 				break;
433 			case 5:
434 				type_to_fetch = type_param&PHP_DNS_HINFO ? DNS_TYPE_HINFO : 0;
435 				break;
436 			case 6:
437 				type_to_fetch = type_param&PHP_DNS_MX    ? DNS_TYPE_MX    : 0;
438 				break;
439 			case 7:
440 				type_to_fetch = type_param&PHP_DNS_TXT   ? DNS_TYPE_TEXT   : 0;
441 				break;
442 			case 8:
443 				type_to_fetch = type_param&PHP_DNS_AAAA	 ? DNS_TYPE_AAAA  : 0;
444 				break;
445 			case 9:
446 				type_to_fetch = type_param&PHP_DNS_SRV   ? DNS_TYPE_SRV   : 0;
447 				break;
448 			case 10:
449 				type_to_fetch = type_param&PHP_DNS_NAPTR ? DNS_TYPE_NAPTR : 0;
450 				break;
451 			case 11:
452 				type_to_fetch = type_param&PHP_DNS_A6	 ? DNS_TYPE_A6 : 0;
453 				break;
454 			case PHP_DNS_NUM_TYPES:
455 				store_results = 0;
456 				continue;
457 			default:
458 			case (PHP_DNS_NUM_TYPES + 1):
459 				type_to_fetch = DNS_TYPE_ANY;
460 				break;
461 		}
462 
463 		if (type_to_fetch) {
464 			status = DnsQuery_A(hostname, type_to_fetch, DNS_QUERY_STANDARD, NULL, &pResult, NULL);
465 
466 			if (status) {
467 				if (status == DNS_INFO_NO_RECORDS || status == DNS_ERROR_RCODE_NAME_ERROR) {
468 					continue;
469 				} else {
470 					php_error_docref(NULL, E_WARNING, "DNS Query failed");
471 					zend_array_destroy(Z_ARR_P(return_value));
472 					RETURN_FALSE;
473 				}
474 			}
475 
476 			for (pRec = pResult; pRec; pRec = pRec->pNext) {
477 				zval retval;
478 
479 				if (pRec->Flags.S.Section == DnsSectionAnswer) {
480 					php_parserr(pRec, type_to_fetch, store_results, raw, &retval);
481 					if (!Z_ISUNDEF(retval) && store_results) {
482 						add_next_index_zval(return_value, &retval);
483 					}
484 				}
485 
486 				if (authns && pRec->Flags.S.Section == DnsSectionAuthority) {
487 
488 					php_parserr(pRec, type_to_fetch, 1, raw, &retval);
489 					if (!Z_ISUNDEF(retval)) {
490 						add_next_index_zval(authns, &retval);
491 					}
492 				}
493 
494 /* Stupid typo in PSDK 6.1, WinDNS.h(1258)... */
495 #ifndef DnsSectionAdditional
496 # ifdef DnsSectionAddtional
497 #  define DnsSectionAdditional DnsSectionAddtional
498 # else
499 # define DnsSectionAdditional 3
500 # endif
501 #endif
502 				if (addtl && pRec->Flags.S.Section == DnsSectionAdditional) {
503 					php_parserr(pRec, type_to_fetch, 1, raw, &retval);
504 					if (!Z_ISUNDEF(retval)) {
505 						add_next_index_zval(addtl, &retval);
506 					}
507 				}
508 			}
509 			/* Free memory allocated for DNS records. */
510 			DnsRecordListFree(pResult, DnsFreeRecordListDeep);
511 		}
512 	}
513 }
514 /* }}} */
515