Coverage Report

Created: 2025-06-13 06:43

/src/php-src/ext/standard/dns.c
Line
Count
Source (jump to first uncovered line)
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: The typical suspects                                        |
14
   |          Pollita <pollita@php.net>                                   |
15
   |          Marcus Boerger <helly@php.net>                              |
16
   +----------------------------------------------------------------------+
17
 */
18
19
/* {{{ includes */
20
#include "php.h"
21
#include "php_network.h"
22
23
#ifdef HAVE_SYS_SOCKET_H
24
#include <sys/socket.h>
25
#endif
26
27
#ifdef PHP_WIN32
28
# include <winsock2.h>
29
# include <windows.h>
30
# include <Ws2tcpip.h>
31
#else
32
#include <netinet/in.h>
33
#ifdef HAVE_ARPA_INET_H
34
#include <arpa/inet.h>
35
#endif
36
#include <netdb.h>
37
#ifdef _OSD_POSIX
38
#undef STATUS
39
#undef T_UNSPEC
40
#endif
41
#ifdef HAVE_ARPA_NAMESER_H
42
#ifdef __APPLE__
43
# define BIND_8_COMPAT 1
44
#endif
45
#include <arpa/nameser.h>
46
#endif
47
#ifdef HAVE_RESOLV_H
48
#include <resolv.h>
49
#if defined(__HAIKU__)
50
extern void __res_ndestroy(res_state statp);
51
#define res_ndestroy __res_ndestroy
52
#endif
53
#endif
54
#ifdef HAVE_DNS_H
55
#include <dns.h>
56
#endif
57
#endif
58
59
#ifndef MAXHOSTNAMELEN
60
#define MAXHOSTNAMELEN 255
61
#endif
62
63
/* For the local hostname obtained via gethostname which is different from the
64
   dns-related MAXHOSTNAMELEN constant above */
65
#ifndef HOST_NAME_MAX
66
#define HOST_NAME_MAX 255
67
#endif
68
69
#include "php_dns.h"
70
71
/* type compat */
72
#ifndef DNS_T_A
73
0
#define DNS_T_A   1
74
#endif
75
#ifndef DNS_T_NS
76
0
#define DNS_T_NS  2
77
#endif
78
#ifndef DNS_T_CNAME
79
0
#define DNS_T_CNAME 5
80
#endif
81
#ifndef DNS_T_SOA
82
0
#define DNS_T_SOA 6
83
#endif
84
#ifndef DNS_T_PTR
85
0
#define DNS_T_PTR 12
86
#endif
87
#ifndef DNS_T_HINFO
88
0
#define DNS_T_HINFO 13
89
#endif
90
#ifndef DNS_T_MINFO
91
#define DNS_T_MINFO 14
92
#endif
93
#ifndef DNS_T_MX
94
0
#define DNS_T_MX  15
95
#endif
96
#ifndef DNS_T_TXT
97
0
#define DNS_T_TXT 16
98
#endif
99
#ifndef DNS_T_AAAA
100
0
#define DNS_T_AAAA  28
101
#endif
102
#ifndef DNS_T_SRV
103
0
#define DNS_T_SRV 33
104
#endif
105
#ifndef DNS_T_NAPTR
106
0
#define DNS_T_NAPTR 35
107
#endif
108
#ifndef DNS_T_A6
109
0
#define DNS_T_A6  38
110
#endif
111
#ifndef DNS_T_CAA
112
0
#define DNS_T_CAA 257
113
#endif
114
115
#ifndef DNS_T_ANY
116
0
#define DNS_T_ANY 255
117
#endif
118
/* }}} */
119
120
static zend_string *php_gethostbyaddr(char *ip);
121
static zend_string *php_gethostbyname(char *name);
122
123
#ifdef HAVE_GETHOSTNAME
124
/* {{{ Get the host name of the current machine */
125
PHP_FUNCTION(gethostname)
126
0
{
127
0
  char buf[HOST_NAME_MAX + 1];
128
129
0
  ZEND_PARSE_PARAMETERS_NONE();
130
131
0
  if (gethostname(buf, sizeof(buf))) {
132
0
    php_error_docref(NULL, E_WARNING, "Unable to fetch host [%d]: %s", errno, strerror(errno));
133
0
    RETURN_FALSE;
134
0
  }
135
136
0
  RETURN_STRING(buf);
137
0
}
138
/* }}} */
139
#endif
140
141
/* TODO: Reimplement the gethostby* functions using the new winxp+ API, in dns_win32.c, then
142
 we can have a dns.c, dns_unix.c and dns_win32.c instead of a messy dns.c full of #ifdef
143
*/
144
145
/* {{{ Get the Internet host name corresponding to a given IP address */
146
PHP_FUNCTION(gethostbyaddr)
147
0
{
148
0
  char *addr;
149
0
  size_t addr_len;
150
0
  zend_string *hostname;
151
152
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
153
0
    Z_PARAM_PATH(addr, addr_len)
154
0
  ZEND_PARSE_PARAMETERS_END();
155
156
0
  hostname = php_gethostbyaddr(addr);
157
158
0
  if (hostname == NULL) {
159
0
#ifdef HAVE_IPV6
160
0
    php_error_docref(NULL, E_WARNING, "Address is not a valid IPv4 or IPv6 address");
161
#else
162
    php_error_docref(NULL, E_WARNING, "Address is not in a.b.c.d form");
163
#endif
164
0
    RETVAL_FALSE;
165
0
  } else {
166
0
    RETVAL_NEW_STR(hostname);
167
0
  }
168
0
}
169
/* }}} */
170
171
/* {{{ php_gethostbyaddr */
172
static zend_string *php_gethostbyaddr(char *ip)
173
0
{
174
0
#ifdef HAVE_IPV6
175
0
  struct sockaddr_in sa4;
176
0
  struct sockaddr_in6 sa6;
177
0
  char out[NI_MAXHOST];
178
0
  memset(&sa4, 0, sizeof(struct sockaddr_in));
179
0
  memset(&sa6, 0, sizeof(struct sockaddr_in6));
180
181
0
  if (inet_pton(AF_INET6, ip, &sa6.sin6_addr)) {
182
0
    sa6.sin6_family = AF_INET6;
183
184
0
    if (getnameinfo((struct sockaddr *)&sa6, sizeof(sa6), out, sizeof(out), NULL, 0, NI_NAMEREQD) != 0) {
185
0
      return zend_string_init(ip, strlen(ip), 0);
186
0
    }
187
0
    return zend_string_init(out, strlen(out), 0);
188
0
  } else if (inet_pton(AF_INET, ip, &sa4.sin_addr)) {
189
0
    sa4.sin_family = AF_INET;
190
191
0
    if (getnameinfo((struct sockaddr *)&sa4, sizeof(sa4), out, sizeof(out), NULL, 0, NI_NAMEREQD) != 0) {
192
0
      return zend_string_init(ip, strlen(ip), 0);
193
0
    }
194
0
    return zend_string_init(out, strlen(out), 0);
195
0
  }
196
0
  return NULL; /* not a valid IP */
197
#else
198
  struct in_addr addr;
199
  struct hostent *hp;
200
201
  addr.s_addr = inet_addr(ip);
202
203
  if (addr.s_addr == -1) {
204
    return NULL;
205
  }
206
207
  hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET);
208
209
  if (!hp || hp->h_name == NULL || hp->h_name[0] == '\0') {
210
    return zend_string_init(ip, strlen(ip), 0);
211
  }
212
213
  return zend_string_init(hp->h_name, strlen(hp->h_name), 0);
214
#endif
215
0
}
216
/* }}} */
217
218
/* {{{ Get the IP address corresponding to a given Internet host name */
219
PHP_FUNCTION(gethostbyname)
220
0
{
221
0
  char *hostname;
222
0
  size_t hostname_len;
223
0
  zend_string *ipaddr;
224
225
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
226
0
    Z_PARAM_PATH(hostname, hostname_len)
227
0
  ZEND_PARSE_PARAMETERS_END();
228
229
0
  if (hostname_len > MAXFQDNLEN) {
230
    /* name too long, protect from CVE-2015-0235 */
231
0
    php_error_docref(NULL, E_WARNING, "Host name cannot be longer than %d characters", MAXFQDNLEN);
232
0
    RETURN_STRINGL(hostname, hostname_len);
233
0
  }
234
235
0
  if (!(ipaddr = php_gethostbyname(hostname))) {
236
0
    php_error_docref(NULL, E_WARNING, "Host name to ip failed %s", hostname);
237
0
    RETURN_STRINGL(hostname, hostname_len);
238
0
  } else {
239
0
    RETURN_NEW_STR(ipaddr);
240
0
  }
241
0
}
242
/* }}} */
243
244
/* {{{ Return a list of IP addresses that a given hostname resolves to. */
245
PHP_FUNCTION(gethostbynamel)
246
0
{
247
0
  char *hostname;
248
0
  size_t hostname_len;
249
0
  struct hostent *hp;
250
0
  struct in_addr in;
251
0
  int i;
252
0
  char addr4[INET_ADDRSTRLEN];
253
254
0
  ZEND_PARSE_PARAMETERS_START(1, 1)
255
0
    Z_PARAM_PATH(hostname, hostname_len)
256
0
  ZEND_PARSE_PARAMETERS_END();
257
258
0
  if (hostname_len > MAXFQDNLEN) {
259
    /* name too long, protect from CVE-2015-0235 */
260
0
    php_error_docref(NULL, E_WARNING, "Host name cannot be longer than %d characters", MAXFQDNLEN);
261
0
    RETURN_FALSE;
262
0
  }
263
264
0
  hp = php_network_gethostbyname(hostname);
265
0
  if (!hp) {
266
0
    RETURN_FALSE;
267
0
  }
268
269
0
  array_init(return_value);
270
271
0
  for (i = 0;; i++) {
272
    /* On macos h_addr_list entries may be misaligned. */
273
0
    const char *ipaddr;
274
0
    struct in_addr *h_addr_entry; /* Don't call this h_addr, it's a macro! */
275
0
    memcpy(&h_addr_entry, &hp->h_addr_list[i], sizeof(struct in_addr *));
276
0
    if (!h_addr_entry) {
277
0
      return;
278
0
    }
279
280
0
    in = *h_addr_entry;
281
0
    if (!(ipaddr = inet_ntop(AF_INET, &in, addr4, INET_ADDRSTRLEN))) {
282
      /* unlikely regarding (too) long hostname and protocols but checking still */
283
0
      php_error_docref(NULL, E_WARNING, "Host name to ip failed %s", hostname);
284
0
      continue;
285
0
    } else {
286
0
      add_next_index_string(return_value, ipaddr);
287
0
    }
288
0
  }
289
0
}
290
/* }}} */
291
292
/* {{{ php_gethostbyname */
293
static zend_string *php_gethostbyname(char *name)
294
0
{
295
0
  struct hostent *hp;
296
0
  struct in_addr *h_addr_0; /* Don't call this h_addr, it's a macro! */
297
0
  struct in_addr in;
298
0
  char addr4[INET_ADDRSTRLEN];
299
0
  const char *address;
300
301
0
  hp = php_network_gethostbyname(name);
302
0
  if (!hp) {
303
0
    return zend_string_init(name, strlen(name), 0);
304
0
  }
305
306
  /* On macos h_addr_list entries may be misaligned. */
307
0
  memcpy(&h_addr_0, &hp->h_addr_list[0], sizeof(struct in_addr *));
308
0
  if (!h_addr_0) {
309
0
    return zend_string_init(name, strlen(name), 0);
310
0
  }
311
312
0
  memcpy(&in.s_addr, h_addr_0, sizeof(in.s_addr));
313
314
0
  if (!(address = inet_ntop(AF_INET, &in, addr4, INET_ADDRSTRLEN))) {
315
0
    return NULL;
316
0
  }
317
318
0
  return zend_string_init(address, strlen(address), 0);
319
0
}
320
/* }}} */
321
322
/* Note: These functions are defined in ext/standard/dns_win32.c for Windows! */
323
#if !defined(PHP_WIN32) && defined(HAVE_DNS_SEARCH_FUNC)
324
325
#ifndef HFIXEDSZ
326
#define HFIXEDSZ        12      /* fixed data in header <arpa/nameser.h> */
327
#endif /* HFIXEDSZ */
328
329
#ifndef QFIXEDSZ
330
#define QFIXEDSZ        4       /* fixed data in query <arpa/nameser.h> */
331
#endif /* QFIXEDSZ */
332
333
#undef MAXHOSTNAMELEN
334
#define MAXHOSTNAMELEN  1024
335
336
#ifndef MAXRESOURCERECORDS
337
#define MAXRESOURCERECORDS  64
338
#endif /* MAXRESOURCERECORDS */
339
340
typedef union {
341
  HEADER qb1;
342
  uint8_t qb2[65536];
343
} querybuf;
344
345
/* just a hack to free resources allocated by glibc in __res_nsend()
346
 * See also:
347
 *   res_thread_freeres() in glibc/resolv/res_init.c
348
 *   __libc_res_nsend()   in resolv/res_send.c
349
 * */
350
351
#if defined(__GLIBC__) && !defined(HAVE_DEPRECATED_DNS_FUNCS)
352
0
#define php_dns_free_res(__res__) _php_dns_free_res(__res__)
353
0
static void _php_dns_free_res(struct __res_state *res) { /* {{{ */
354
0
  int ns;
355
0
  for (ns = 0; ns < MAXNS; ns++) {
356
0
    if (res->_u._ext.nsaddrs[ns] != NULL) {
357
0
      free (res->_u._ext.nsaddrs[ns]);
358
0
      res->_u._ext.nsaddrs[ns] = NULL;
359
0
    }
360
0
  }
361
0
} /* }}} */
362
#else
363
#define php_dns_free_res(__res__)
364
#endif
365
366
/* {{{ Check DNS records corresponding to a given Internet host name or IP address */
367
PHP_FUNCTION(dns_check_record)
368
0
{
369
0
  HEADER *hp;
370
0
  querybuf answer = {0};
371
0
  char *hostname;
372
0
  size_t hostname_len;
373
0
  zend_string *rectype = NULL;
374
0
  int type = DNS_T_MX, i;
375
#if defined(HAVE_DNS_SEARCH)
376
  struct sockaddr_storage from;
377
  uint32_t fromsize = sizeof(from);
378
  dns_handle_t handle;
379
#elif defined(HAVE_RES_NSEARCH)
380
  struct __res_state state;
381
0
  struct __res_state *handle = &state;
382
0
#endif
383
384
0
  ZEND_PARSE_PARAMETERS_START(1, 2)
385
0
    Z_PARAM_STRING(hostname, hostname_len)
386
0
    Z_PARAM_OPTIONAL
387
0
    Z_PARAM_STR(rectype)
388
0
  ZEND_PARSE_PARAMETERS_END();
389
390
0
  if (hostname_len == 0) {
391
0
    zend_argument_must_not_be_empty_error(1);
392
0
    RETURN_THROWS();
393
0
  }
394
395
0
  if (rectype) {
396
0
    if (zend_string_equals_literal_ci(rectype, "A")) type = DNS_T_A;
397
0
    else if (zend_string_equals_literal_ci(rectype, "NS")) type = DNS_T_NS;
398
0
    else if (zend_string_equals_literal_ci(rectype, "MX")) type = DNS_T_MX;
399
0
    else if (zend_string_equals_literal_ci(rectype, "PTR")) type = DNS_T_PTR;
400
0
    else if (zend_string_equals_literal_ci(rectype, "ANY")) type = DNS_T_ANY;
401
0
    else if (zend_string_equals_literal_ci(rectype, "SOA")) type = DNS_T_SOA;
402
0
    else if (zend_string_equals_literal_ci(rectype, "CAA")) type = DNS_T_CAA;
403
0
    else if (zend_string_equals_literal_ci(rectype, "TXT")) type = DNS_T_TXT;
404
0
    else if (zend_string_equals_literal_ci(rectype, "CNAME")) type = DNS_T_CNAME;
405
0
    else if (zend_string_equals_literal_ci(rectype, "AAAA")) type = DNS_T_AAAA;
406
0
    else if (zend_string_equals_literal_ci(rectype, "SRV")) type = DNS_T_SRV;
407
0
    else if (zend_string_equals_literal_ci(rectype, "NAPTR")) type = DNS_T_NAPTR;
408
0
    else if (zend_string_equals_literal_ci(rectype, "A6")) type = DNS_T_A6;
409
0
    else {
410
0
      zend_argument_value_error(2, "must be a valid DNS record type");
411
0
      RETURN_THROWS();
412
0
    }
413
0
  }
414
415
#if defined(HAVE_DNS_SEARCH)
416
  handle = dns_open(NULL);
417
  if (handle == NULL) {
418
    RETURN_FALSE;
419
  }
420
#elif defined(HAVE_RES_NSEARCH)
421
0
  memset(&state, 0, sizeof(state));
422
0
  if (res_ninit(handle)) {
423
0
      RETURN_FALSE;
424
0
  }
425
#else
426
  res_init();
427
#endif
428
429
0
  i = php_dns_search(handle, hostname, C_IN, type, answer.qb2, sizeof answer);
430
0
  php_dns_free_handle(handle);
431
432
0
  if (i < 0) {
433
0
    RETURN_FALSE;
434
0
  }
435
0
  hp = (HEADER *)&answer;
436
0
  RETURN_BOOL(ntohs(hp->ancount) != 0);
437
0
}
438
/* }}} */
439
440
#ifdef HAVE_FULL_DNS_FUNCS
441
442
0
#define CHECKCP(n) do { \
443
0
  if (cp + n > end) { \
444
0
    return NULL; \
445
0
  } \
446
0
} while (0)
447
448
/* {{{ php_parserr */
449
static uint8_t *php_parserr(uint8_t *cp, uint8_t *end, querybuf *answer, int type_to_fetch, int store, bool raw, zval *subarray)
450
0
{
451
0
  u_short type, class, dlen;
452
0
  u_long ttl;
453
0
  long n, i;
454
0
  u_short s;
455
0
  uint8_t *tp, *p;
456
0
  char name[MAXHOSTNAMELEN] = {0};
457
0
  int have_v6_break = 0, in_v6_break = 0;
458
459
0
  ZVAL_UNDEF(subarray);
460
461
0
  n = dn_expand(answer->qb2, end, cp, name, sizeof(name) - 2);
462
0
  if (n < 0) {
463
0
    return NULL;
464
0
  }
465
0
  cp += n;
466
467
0
  CHECKCP(10);
468
0
  GETSHORT(type, cp);
469
0
  GETSHORT(class, cp);
470
0
  GETLONG(ttl, cp);
471
0
  GETSHORT(dlen, cp);
472
0
  CHECKCP(dlen);
473
0
  if (dlen == 0) {
474
    /* No data in the response - nothing to do */
475
0
    return NULL;
476
0
  }
477
0
  if (type_to_fetch != DNS_T_ANY && type != type_to_fetch) {
478
0
    cp += dlen;
479
0
    return cp;
480
0
  }
481
482
0
  if (!store) {
483
0
    cp += dlen;
484
0
    return cp;
485
0
  }
486
487
0
  array_init(subarray);
488
489
0
  add_assoc_string(subarray, "host", name);
490
0
  add_assoc_string(subarray, "class", "IN");
491
0
  add_assoc_long(subarray, "ttl", ttl);
492
0
  (void) class;
493
494
0
  if (raw) {
495
0
    add_assoc_long(subarray, "type", type);
496
0
    add_assoc_stringl(subarray, "data", (char*) cp, (uint32_t) dlen);
497
0
    cp += dlen;
498
0
    return cp;
499
0
  }
500
501
0
  switch (type) {
502
0
    case DNS_T_A:
503
0
      CHECKCP(4);
504
0
      add_assoc_string(subarray, "type", "A");
505
0
      snprintf(name, sizeof(name), "%d.%d.%d.%d", cp[0], cp[1], cp[2], cp[3]);
506
0
      add_assoc_string(subarray, "ip", name);
507
0
      cp += dlen;
508
0
      break;
509
0
    case DNS_T_MX:
510
0
      CHECKCP(2);
511
0
      add_assoc_string(subarray, "type", "MX");
512
0
      GETSHORT(n, cp);
513
0
      add_assoc_long(subarray, "pri", n);
514
0
      ZEND_FALLTHROUGH;
515
0
    case DNS_T_CNAME:
516
0
      if (type == DNS_T_CNAME) {
517
0
        add_assoc_string(subarray, "type", "CNAME");
518
0
      }
519
0
      ZEND_FALLTHROUGH;
520
0
    case DNS_T_NS:
521
0
      if (type == DNS_T_NS) {
522
0
        add_assoc_string(subarray, "type", "NS");
523
0
      }
524
0
      ZEND_FALLTHROUGH;
525
0
    case DNS_T_PTR:
526
0
      if (type == DNS_T_PTR) {
527
0
        add_assoc_string(subarray, "type", "PTR");
528
0
      }
529
0
      n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
530
0
      if (n < 0) {
531
0
        return NULL;
532
0
      }
533
0
      cp += n;
534
0
      add_assoc_string(subarray, "target", name);
535
0
      break;
536
0
    case DNS_T_HINFO:
537
      /* See RFC 1010 for values */
538
0
      add_assoc_string(subarray, "type", "HINFO");
539
0
      CHECKCP(1);
540
0
      n = *cp & 0xFF;
541
0
      cp++;
542
0
      CHECKCP(n);
543
0
      add_assoc_stringl(subarray, "cpu", (char*)cp, n);
544
0
      cp += n;
545
0
      CHECKCP(1);
546
0
      n = *cp & 0xFF;
547
0
      cp++;
548
0
      CHECKCP(n);
549
0
      add_assoc_stringl(subarray, "os", (char*)cp, n);
550
0
      cp += n;
551
0
      break;
552
0
    case DNS_T_CAA:
553
      /* See RFC 6844 for values https://tools.ietf.org/html/rfc6844 */
554
0
      add_assoc_string(subarray, "type", "CAA");
555
      // 1 flag byte
556
0
      CHECKCP(1);
557
0
      n = *cp & 0xFF;
558
0
      add_assoc_long(subarray, "flags", n);
559
0
      cp++;
560
      // Tag length (1 byte)
561
0
      CHECKCP(1);
562
0
      n = *cp & 0xFF;
563
0
      cp++;
564
0
      CHECKCP(n);
565
0
      add_assoc_stringl(subarray, "tag", (char*)cp, n);
566
0
      cp += n;
567
0
      if ( (size_t) dlen < ((size_t)n) + 2 ) {
568
0
        return NULL;
569
0
      }
570
0
      n = dlen - n - 2;
571
0
      CHECKCP(n);
572
0
      add_assoc_stringl(subarray, "value", (char*)cp, n);
573
0
      cp += n;
574
0
      break;
575
0
    case DNS_T_TXT:
576
0
      {
577
0
        int l1 = 0, l2 = 0;
578
0
        zval entries;
579
0
        zend_string *tp;
580
581
0
        add_assoc_string(subarray, "type", "TXT");
582
0
        tp = zend_string_alloc(dlen, 0);
583
584
0
        array_init(&entries);
585
586
0
        while (l1 < dlen) {
587
0
          n = cp[l1];
588
0
          if ((l1 + n) >= dlen) {
589
            // Invalid chunk length, truncate
590
0
            n = dlen - (l1 + 1);
591
0
          }
592
0
          if (n) {
593
0
            memcpy(ZSTR_VAL(tp) + l2 , cp + l1 + 1, n);
594
0
            add_next_index_stringl(&entries, (char *) cp + l1 + 1, n);
595
0
          }
596
0
          l1 = l1 + n + 1;
597
0
          l2 = l2 + n;
598
0
        }
599
0
        ZSTR_VAL(tp)[l2] = '\0';
600
0
        ZSTR_LEN(tp) = l2;
601
0
        cp += dlen;
602
603
0
        add_assoc_str(subarray, "txt", tp);
604
0
        add_assoc_zval(subarray, "entries", &entries);
605
0
      }
606
0
      break;
607
0
    case DNS_T_SOA:
608
0
      add_assoc_string(subarray, "type", "SOA");
609
0
      n = dn_expand(answer->qb2, end, cp, name, (sizeof name) -2);
610
0
      if (n < 0) {
611
0
        return NULL;
612
0
      }
613
0
      cp += n;
614
0
      add_assoc_string(subarray, "mname", name);
615
0
      n = dn_expand(answer->qb2, end, cp, name, (sizeof name) -2);
616
0
      if (n < 0) {
617
0
        return NULL;
618
0
      }
619
0
      cp += n;
620
0
      add_assoc_string(subarray, "rname", name);
621
0
      CHECKCP(5*4);
622
0
      GETLONG(n, cp);
623
0
      add_assoc_long(subarray, "serial", n);
624
0
      GETLONG(n, cp);
625
0
      add_assoc_long(subarray, "refresh", n);
626
0
      GETLONG(n, cp);
627
0
      add_assoc_long(subarray, "retry", n);
628
0
      GETLONG(n, cp);
629
0
      add_assoc_long(subarray, "expire", n);
630
0
      GETLONG(n, cp);
631
0
      add_assoc_long(subarray, "minimum-ttl", n);
632
0
      break;
633
0
    case DNS_T_AAAA:
634
0
      tp = (uint8_t*)name;
635
0
      CHECKCP(8*2);
636
0
      for(i=0; i < 8; i++) {
637
0
        GETSHORT(s, cp);
638
0
        if (s != 0) {
639
0
          if (tp > (uint8_t *)name) {
640
0
            in_v6_break = 0;
641
0
            tp[0] = ':';
642
0
            tp++;
643
0
          }
644
0
          tp += snprintf((char*)tp, sizeof(name) - (tp - (uint8_t *) name), "%x", s);
645
0
        } else {
646
0
          if (!have_v6_break) {
647
0
            have_v6_break = 1;
648
0
            in_v6_break = 1;
649
0
            tp[0] = ':';
650
0
            tp++;
651
0
          } else if (!in_v6_break) {
652
0
            tp[0] = ':';
653
0
            tp++;
654
0
            tp[0] = '0';
655
0
            tp++;
656
0
          }
657
0
        }
658
0
      }
659
0
      if (have_v6_break && in_v6_break) {
660
0
        tp[0] = ':';
661
0
        tp++;
662
0
      }
663
0
      tp[0] = '\0';
664
0
      add_assoc_string(subarray, "type", "AAAA");
665
0
      add_assoc_string(subarray, "ipv6", name);
666
0
      break;
667
0
    case DNS_T_A6:
668
0
      p = cp;
669
0
      add_assoc_string(subarray, "type", "A6");
670
0
      CHECKCP(1);
671
0
      n = ((int)cp[0]) & 0xFF;
672
0
      cp++;
673
0
      add_assoc_long(subarray, "masklen", n);
674
0
      tp = (uint8_t*)name;
675
0
      if (n > 15) {
676
0
        have_v6_break = 1;
677
0
        in_v6_break = 1;
678
0
        tp[0] = ':';
679
0
        tp++;
680
0
      }
681
0
      if (n % 16 > 8) {
682
        /* Partial short */
683
0
        if (cp[0] != 0) {
684
0
          if (tp > (uint8_t *)name) {
685
0
            in_v6_break = 0;
686
0
            tp[0] = ':';
687
0
            tp++;
688
0
          }
689
0
          snprintf((char*)tp, sizeof(name) - (tp - (uint8_t *) name), "%x", cp[0] & 0xFF);
690
0
        } else {
691
0
          if (!have_v6_break) {
692
0
            have_v6_break = 1;
693
0
            in_v6_break = 1;
694
0
            tp[0] = ':';
695
0
            tp++;
696
0
          } else if (!in_v6_break) {
697
0
            tp[0] = ':';
698
0
            tp++;
699
0
            tp[0] = '0';
700
0
            tp++;
701
0
          }
702
0
        }
703
0
        cp++;
704
0
      }
705
0
      for (i = (n + 8) / 16; i < 8; i++) {
706
0
        CHECKCP(2);
707
0
        GETSHORT(s, cp);
708
0
        if (s != 0) {
709
0
          if (tp > (uint8_t *)name) {
710
0
            in_v6_break = 0;
711
0
            tp[0] = ':';
712
0
            tp++;
713
0
          }
714
0
          tp += snprintf((char*)tp, sizeof(name) - (tp - (uint8_t *) name),"%x",s);
715
0
        } else {
716
0
          if (!have_v6_break) {
717
0
            have_v6_break = 1;
718
0
            in_v6_break = 1;
719
0
            tp[0] = ':';
720
0
            tp++;
721
0
          } else if (!in_v6_break) {
722
0
            tp[0] = ':';
723
0
            tp++;
724
0
            tp[0] = '0';
725
0
            tp++;
726
0
          }
727
0
        }
728
0
      }
729
0
      if (have_v6_break && in_v6_break) {
730
0
        tp[0] = ':';
731
0
        tp++;
732
0
      }
733
0
      tp[0] = '\0';
734
0
      add_assoc_string(subarray, "ipv6", name);
735
0
      if (cp < p + dlen) {
736
0
        n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
737
0
        if (n < 0) {
738
0
          return NULL;
739
0
        }
740
0
        cp += n;
741
0
        add_assoc_string(subarray, "chain", name);
742
0
      }
743
0
      break;
744
0
    case DNS_T_SRV:
745
0
      CHECKCP(3*2);
746
0
      add_assoc_string(subarray, "type", "SRV");
747
0
      GETSHORT(n, cp);
748
0
      add_assoc_long(subarray, "pri", n);
749
0
      GETSHORT(n, cp);
750
0
      add_assoc_long(subarray, "weight", n);
751
0
      GETSHORT(n, cp);
752
0
      add_assoc_long(subarray, "port", n);
753
0
      n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
754
0
      if (n < 0) {
755
0
        return NULL;
756
0
      }
757
0
      cp += n;
758
0
      add_assoc_string(subarray, "target", name);
759
0
      break;
760
0
    case DNS_T_NAPTR:
761
0
      CHECKCP(2*2);
762
0
      add_assoc_string(subarray, "type", "NAPTR");
763
0
      GETSHORT(n, cp);
764
0
      add_assoc_long(subarray, "order", n);
765
0
      GETSHORT(n, cp);
766
0
      add_assoc_long(subarray, "pref", n);
767
768
0
      CHECKCP(1);
769
0
      n = (cp[0] & 0xFF);
770
0
      cp++;
771
0
      CHECKCP(n);
772
0
      add_assoc_stringl(subarray, "flags", (char*)cp, n);
773
0
      cp += n;
774
775
0
      CHECKCP(1);
776
0
      n = (cp[0] & 0xFF);
777
0
      cp++;
778
0
      CHECKCP(n);
779
0
      add_assoc_stringl(subarray, "services", (char*)cp, n);
780
0
      cp += n;
781
782
0
      CHECKCP(1);
783
0
      n = (cp[0] & 0xFF);
784
0
      cp++;
785
0
      CHECKCP(n);
786
0
      add_assoc_stringl(subarray, "regex", (char*)cp, n);
787
0
      cp += n;
788
789
0
      n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
790
0
      if (n < 0) {
791
0
        return NULL;
792
0
      }
793
0
      cp += n;
794
0
      add_assoc_string(subarray, "replacement", name);
795
0
      break;
796
0
    default:
797
0
      zval_ptr_dtor(subarray);
798
0
      ZVAL_UNDEF(subarray);
799
0
      cp += dlen;
800
0
      break;
801
0
  }
802
803
0
  return cp;
804
0
}
805
/* }}} */
806
807
/* {{{ Get any Resource Record corresponding to a given Internet host name */
808
PHP_FUNCTION(dns_get_record)
809
0
{
810
0
  char *hostname;
811
0
  size_t hostname_len;
812
0
  zend_long type_param = PHP_DNS_ANY;
813
0
  zval *authns = NULL, *addtl = NULL;
814
0
  int type_to_fetch;
815
0
  int dns_errno;
816
#if defined(HAVE_DNS_SEARCH)
817
  struct sockaddr_storage from;
818
  uint32_t fromsize = sizeof(from);
819
  dns_handle_t handle;
820
#elif defined(HAVE_RES_NSEARCH)
821
  struct __res_state state;
822
0
  struct __res_state *handle = &state;
823
0
#endif
824
0
  HEADER *hp;
825
0
  querybuf answer = {0};
826
0
  uint8_t *cp = NULL, *end = NULL;
827
0
  int n, qd, an, ns = 0, ar = 0;
828
0
  int type, first_query = 1, store_results = 1;
829
0
  bool raw = 0;
830
831
0
  ZEND_PARSE_PARAMETERS_START(1, 5)
832
0
    Z_PARAM_STRING(hostname, hostname_len)
833
0
    Z_PARAM_OPTIONAL
834
0
    Z_PARAM_LONG(type_param)
835
0
    Z_PARAM_ZVAL(authns)
836
0
    Z_PARAM_ZVAL(addtl)
837
0
    Z_PARAM_BOOL(raw)
838
0
  ZEND_PARSE_PARAMETERS_END();
839
840
0
  if (authns) {
841
0
    authns = zend_try_array_init(authns);
842
0
    if (!authns) {
843
0
      RETURN_THROWS();
844
0
    }
845
0
  }
846
0
  if (addtl) {
847
0
    addtl = zend_try_array_init(addtl);
848
0
    if (!addtl) {
849
0
      RETURN_THROWS();
850
0
    }
851
0
  }
852
853
0
  if (!raw) {
854
0
    if ((type_param & ~PHP_DNS_ALL) && (type_param != PHP_DNS_ANY)) {
855
0
      zend_argument_value_error(2, "must be a DNS_* constant");
856
0
      RETURN_THROWS();
857
0
    }
858
0
  } else {
859
0
    if ((type_param < 1) || (type_param > 0xFFFF)) {
860
0
      zend_argument_value_error(2, "must be between 1 and 65535 when argument #5 ($raw) is true");
861
0
      RETURN_THROWS();
862
0
    }
863
0
  }
864
865
  /* Initialize the return array */
866
0
  array_init(return_value);
867
868
  /* - We emulate an or'ed type mask by querying type by type. (Steps 0 - NUMTYPES-1 )
869
   *   If additional info is wanted we check again with DNS_T_ANY (step NUMTYPES / NUMTYPES+1 )
870
   *   store_results is used to skip storing the results retrieved in step
871
   *   NUMTYPES+1 when results were already fetched.
872
   * - In case of PHP_DNS_ANY we use the directly fetch DNS_T_ANY. (step NUMTYPES+1 )
873
   * - In case of raw mode, we query only the requested type instead of looping type by type
874
   *   before going with the additional info stuff.
875
   */
876
877
0
  if (raw) {
878
0
    type = -1;
879
0
  } else if (type_param == PHP_DNS_ANY) {
880
0
    type = PHP_DNS_NUM_TYPES + 1;
881
0
  } else {
882
0
    type = 0;
883
0
  }
884
885
0
  for ( ;
886
0
    type < (addtl ? (PHP_DNS_NUM_TYPES + 2) : PHP_DNS_NUM_TYPES) || first_query;
887
0
    type++
888
0
  ) {
889
0
    first_query = 0;
890
0
    switch (type) {
891
0
      case -1: /* raw */
892
0
        type_to_fetch = type_param;
893
        /* skip over the rest and go directly to additional records */
894
0
        type = PHP_DNS_NUM_TYPES - 1;
895
0
        break;
896
0
      case 0:
897
0
        type_to_fetch = type_param&PHP_DNS_A     ? DNS_T_A     : 0;
898
0
        break;
899
0
      case 1:
900
0
        type_to_fetch = type_param&PHP_DNS_NS    ? DNS_T_NS    : 0;
901
0
        break;
902
0
      case 2:
903
0
        type_to_fetch = type_param&PHP_DNS_CNAME ? DNS_T_CNAME : 0;
904
0
        break;
905
0
      case 3:
906
0
        type_to_fetch = type_param&PHP_DNS_SOA   ? DNS_T_SOA   : 0;
907
0
        break;
908
0
      case 4:
909
0
        type_to_fetch = type_param&PHP_DNS_PTR   ? DNS_T_PTR   : 0;
910
0
        break;
911
0
      case 5:
912
0
        type_to_fetch = type_param&PHP_DNS_HINFO ? DNS_T_HINFO : 0;
913
0
        break;
914
0
      case 6:
915
0
        type_to_fetch = type_param&PHP_DNS_MX    ? DNS_T_MX    : 0;
916
0
        break;
917
0
      case 7:
918
0
        type_to_fetch = type_param&PHP_DNS_TXT   ? DNS_T_TXT   : 0;
919
0
        break;
920
0
      case 8:
921
0
        type_to_fetch = type_param&PHP_DNS_AAAA   ? DNS_T_AAAA  : 0;
922
0
        break;
923
0
      case 9:
924
0
        type_to_fetch = type_param&PHP_DNS_SRV   ? DNS_T_SRV   : 0;
925
0
        break;
926
0
      case 10:
927
0
        type_to_fetch = type_param&PHP_DNS_NAPTR ? DNS_T_NAPTR : 0;
928
0
        break;
929
0
      case 11:
930
0
        type_to_fetch = type_param&PHP_DNS_A6   ? DNS_T_A6 : 0;
931
0
        break;
932
0
      case 12:
933
0
        type_to_fetch = type_param&PHP_DNS_CAA ? DNS_T_CAA : 0;
934
0
        break;
935
0
      case PHP_DNS_NUM_TYPES:
936
0
        store_results = 0;
937
0
        continue;
938
0
      default:
939
0
      case (PHP_DNS_NUM_TYPES + 1):
940
0
        type_to_fetch = DNS_T_ANY;
941
0
        break;
942
0
    }
943
944
0
    if (type_to_fetch) {
945
#if defined(HAVE_DNS_SEARCH)
946
      handle = dns_open(NULL);
947
      if (handle == NULL) {
948
        zend_array_destroy(Z_ARR_P(return_value));
949
        RETURN_FALSE;
950
      }
951
#elif defined(HAVE_RES_NSEARCH)
952
        memset(&state, 0, sizeof(state));
953
0
        if (res_ninit(handle)) {
954
0
          zend_array_destroy(Z_ARR_P(return_value));
955
0
        RETURN_FALSE;
956
0
      }
957
#else
958
      res_init();
959
#endif
960
961
0
      n = php_dns_search(handle, hostname, C_IN, type_to_fetch, answer.qb2, sizeof answer);
962
963
0
      if (n < 0) {
964
0
        dns_errno = php_dns_errno(handle);
965
0
        php_dns_free_handle(handle);
966
0
        switch (dns_errno) {
967
0
          case NO_DATA:
968
0
          case HOST_NOT_FOUND:
969
0
            continue;
970
971
0
          case NO_RECOVERY:
972
0
            php_error_docref(NULL, E_WARNING, "An unexpected server failure occurred.");
973
0
            break;
974
975
0
          case TRY_AGAIN:
976
0
            php_error_docref(NULL, E_WARNING, "A temporary server error occurred.");
977
0
            break;
978
979
0
          default:
980
0
            php_error_docref(NULL, E_WARNING, "DNS Query failed");
981
0
        }
982
0
        zend_array_destroy(Z_ARR_P(return_value));
983
0
        RETURN_FALSE;
984
0
      }
985
986
0
      cp = answer.qb2 + HFIXEDSZ;
987
0
      end = answer.qb2 + n;
988
0
      hp = (HEADER *)&answer;
989
0
      qd = ntohs(hp->qdcount);
990
0
      an = ntohs(hp->ancount);
991
0
      ns = ntohs(hp->nscount);
992
0
      ar = ntohs(hp->arcount);
993
994
      /* Skip QD entries, they're only used by dn_expand later on */
995
0
      while (qd-- > 0) {
996
0
        n = dn_skipname(cp, end);
997
0
        if (n < 0) {
998
0
          php_error_docref(NULL, E_WARNING, "Unable to parse DNS data received");
999
0
          zend_array_destroy(Z_ARR_P(return_value));
1000
0
          php_dns_free_handle(handle);
1001
0
          RETURN_FALSE;
1002
0
        }
1003
0
        cp += n + QFIXEDSZ;
1004
0
      }
1005
1006
      /* YAY! Our real answers! */
1007
0
      while (an-- && cp && cp < end) {
1008
0
        zval retval;
1009
1010
0
        cp = php_parserr(cp, end, &answer, type_to_fetch, store_results, raw, &retval);
1011
0
        if (Z_TYPE(retval) != IS_UNDEF && store_results) {
1012
0
          add_next_index_zval(return_value, &retval);
1013
0
        }
1014
0
      }
1015
1016
0
      if (authns || addtl) {
1017
        /* List of Authoritative Name Servers
1018
         * Process when only requesting addtl so that we can skip through the section
1019
         */
1020
0
        while (ns-- > 0 && cp && cp < end) {
1021
0
          zval retval;
1022
1023
0
          cp = php_parserr(cp, end, &answer, DNS_T_ANY, authns != NULL, raw, &retval);
1024
0
          if (Z_TYPE(retval) != IS_UNDEF) {
1025
0
            add_next_index_zval(authns, &retval);
1026
0
          }
1027
0
        }
1028
0
      }
1029
1030
0
      if (addtl) {
1031
        /* Additional records associated with authoritative name servers */
1032
0
        while (ar-- > 0 && cp && cp < end) {
1033
0
          zval retval;
1034
1035
0
          cp = php_parserr(cp, end, &answer, DNS_T_ANY, 1, raw, &retval);
1036
0
          if (Z_TYPE(retval) != IS_UNDEF) {
1037
0
            add_next_index_zval(addtl, &retval);
1038
0
          }
1039
0
        }
1040
0
      }
1041
0
      php_dns_free_handle(handle);
1042
0
    }
1043
0
  }
1044
0
}
1045
/* }}} */
1046
1047
/* {{{ Get MX records corresponding to a given Internet host name */
1048
PHP_FUNCTION(dns_get_mx)
1049
0
{
1050
0
  char *hostname;
1051
0
  size_t hostname_len;
1052
0
  zval *mx_list, *weight_list = NULL;
1053
0
  int count, qdc;
1054
0
  u_short type, weight;
1055
0
  querybuf answer = {0};
1056
0
  char buf[MAXHOSTNAMELEN] = {0};
1057
0
  HEADER *hp;
1058
0
  uint8_t *cp, *end;
1059
0
  int i;
1060
#if defined(HAVE_DNS_SEARCH)
1061
  struct sockaddr_storage from;
1062
  uint32_t fromsize = sizeof(from);
1063
  dns_handle_t handle;
1064
#elif defined(HAVE_RES_NSEARCH)
1065
  struct __res_state state;
1066
0
  struct __res_state *handle = &state;
1067
0
#endif
1068
1069
0
  ZEND_PARSE_PARAMETERS_START(2, 3)
1070
0
    Z_PARAM_STRING(hostname, hostname_len)
1071
0
    Z_PARAM_ZVAL(mx_list)
1072
0
    Z_PARAM_OPTIONAL
1073
0
    Z_PARAM_ZVAL(weight_list)
1074
0
  ZEND_PARSE_PARAMETERS_END();
1075
1076
0
  mx_list = zend_try_array_init(mx_list);
1077
0
  if (!mx_list) {
1078
0
    RETURN_THROWS();
1079
0
  }
1080
1081
0
  if (weight_list) {
1082
0
    weight_list = zend_try_array_init(weight_list);
1083
0
    if (!weight_list) {
1084
0
      RETURN_THROWS();
1085
0
    }
1086
0
  }
1087
1088
#if defined(HAVE_DNS_SEARCH)
1089
  handle = dns_open(NULL);
1090
  if (handle == NULL) {
1091
    RETURN_FALSE;
1092
  }
1093
#elif defined(HAVE_RES_NSEARCH)
1094
0
  memset(&state, 0, sizeof(state));
1095
0
  if (res_ninit(handle)) {
1096
0
      RETURN_FALSE;
1097
0
  }
1098
#else
1099
  res_init();
1100
#endif
1101
1102
0
  i = php_dns_search(handle, hostname, C_IN, DNS_T_MX, answer.qb2, sizeof(answer));
1103
0
  if (i < 0) {
1104
0
    php_dns_free_handle(handle);
1105
0
    RETURN_FALSE;
1106
0
  }
1107
0
  hp = (HEADER *)&answer;
1108
0
  cp = answer.qb2 + HFIXEDSZ;
1109
0
  end = answer.qb2 + i;
1110
0
  for (qdc = ntohs((unsigned short)hp->qdcount); qdc--; cp += i + QFIXEDSZ) {
1111
0
    if ((i = dn_skipname(cp, end)) < 0 ) {
1112
0
      php_dns_free_handle(handle);
1113
0
      RETURN_FALSE;
1114
0
    }
1115
0
  }
1116
0
  count = ntohs((unsigned short)hp->ancount);
1117
0
  while (--count >= 0 && cp < end) {
1118
0
    if ((i = dn_skipname(cp, end)) < 0 ) {
1119
0
      php_dns_free_handle(handle);
1120
0
      RETURN_FALSE;
1121
0
    }
1122
0
    cp += i;
1123
0
    GETSHORT(type, cp);
1124
0
    cp += INT16SZ + INT32SZ;
1125
0
    GETSHORT(i, cp);
1126
0
    if (type != DNS_T_MX) {
1127
0
      cp += i;
1128
0
      continue;
1129
0
    }
1130
0
    GETSHORT(weight, cp);
1131
0
    if ((i = dn_expand(answer.qb2, end, cp, buf, sizeof(buf)-1)) < 0) {
1132
0
      php_dns_free_handle(handle);
1133
0
      RETURN_FALSE;
1134
0
    }
1135
0
    cp += i;
1136
0
    add_next_index_string(mx_list, buf);
1137
0
    if (weight_list) {
1138
0
      add_next_index_long(weight_list, weight);
1139
0
    }
1140
0
  }
1141
0
  php_dns_free_handle(handle);
1142
0
  RETURN_BOOL(zend_hash_num_elements(Z_ARRVAL_P(mx_list)) != 0);
1143
0
}
1144
/* }}} */
1145
#endif /* HAVE_FULL_DNS_FUNCS */
1146
#endif /* !defined(PHP_WIN32) && HAVE_DNS_SEARCH_FUNC */