Coverage Report

Created: 2026-06-02 06:36

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