de-dup THREAD_LOCAL macros
[hiphop-php.git] / hphp / runtime / ext / std / ext_std_network-posix.cpp
blob46a0868816f6e9375f5b930fa010b0d1d77cf131
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
17 #ifndef _MSC_VER
18 #include "hphp/runtime/ext/std/ext_std_network.h"
19 #include "hphp/runtime/ext/std/ext_std_network-internal.h"
21 #include <arpa/inet.h>
22 #include <arpa/nameser.h>
23 #include <netdb.h>
24 #include <netinet/in.h>
25 #include <resolv.h>
26 #include <sys/socket.h>
28 #include <folly/IPAddress.h>
29 #include <folly/ScopeGuard.h>
31 #include "hphp/runtime/base/builtin-functions.h"
32 #include "hphp/runtime/base/file.h"
33 #include "hphp/runtime/base/runtime-option.h"
34 #include "hphp/runtime/ext/sockets/ext_sockets.h"
35 #include "hphp/runtime/ext/std/ext_std_function.h"
36 #include "hphp/runtime/ext/string/ext_string.h"
37 #include "hphp/runtime/server/server-stats.h"
38 #include "hphp/util/lock.h"
39 #include "hphp/util/network.h"
41 #if defined(__APPLE__)
42 # include <arpa/nameser_compat.h>
43 #include <vector>
44 #endif
47 namespace HPHP {
48 ///////////////////////////////////////////////////////////////////////////////
49 // DNS
51 struct ResolverInit {
52 ResolverInit() : m_res(nullptr) {
53 m_res = (struct __res_state *)calloc(1, sizeof(*m_res));
54 initRes();
56 ~ResolverInit() {
57 if (m_res)
58 free(m_res);
59 m_res = nullptr;
62 struct __res_state *getResolver(void) {
63 initRes();
64 return m_res;
67 static THREAD_LOCAL(ResolverInit, s_res);
68 private:
69 void initRes(void) {
70 if (m_res) {
71 memset(m_res, 0, sizeof(*m_res));
72 if (res_ninit(m_res)) {
73 free(m_res);
74 m_res = nullptr;
79 struct __res_state *m_res;
81 THREAD_LOCAL(ResolverInit, ResolverInit::s_res);
83 /* just a hack to free resources allocated by glibc in __res_nsend()
84 * See also:
85 * res_thread_freeres() in glibc/resolv/res_init.c
86 * __libc_res_nsend() in resolv/res_send.c
87 * */
89 static void php_dns_free_res(struct __res_state *res) {
90 #if defined(__GLIBC__)
91 int ns;
92 for (ns = 0; ns < MAXNS; ns++) {
93 if (res->_u._ext.nsaddrs[ns] != NULL) {
94 free(res->_u._ext.nsaddrs[ns]);
95 res->_u._ext.nsaddrs[ns] = NULL;
98 #endif
101 bool HHVM_FUNCTION(checkdnsrr, const String& host,
102 const String& type /* = null_string */) {
103 int ntype;
104 if (!validate_dns_arguments(host, type, ntype)) {
105 return false;
108 unsigned char ans[MAXPACKET];
109 struct __res_state *res;
110 res = ResolverInit::s_res.get()->getResolver();
111 if (res == NULL) {
112 return false;
115 int i = res_nsearch(res, host.data(), C_IN, ntype, ans, sizeof(ans));
116 res_nclose(res);
117 php_dns_free_res(res);
118 return (i >= 0);
121 typedef union {
122 HEADER qb1;
123 u_char qb2[65536];
124 } querybuf;
126 const StaticString
127 s_host("host"),
128 s_type("type"),
129 s_ip("ip"),
130 s_pri("pri"),
131 s_weight("weight"),
132 s_port("port"),
133 s_order("order"),
134 s_pref("pref"),
135 s_target("target"),
136 s_cpu("cpu"),
137 s_os("os"),
138 s_txt("txt"),
139 s_mname("mname"),
140 s_rname("rname"),
141 s_serial("serial"),
142 s_refresh("refresh"),
143 s_retry("retry"),
144 s_expire("expire"),
145 s_minimum_ttl("minimum-ttl"),
146 s_ipv6("ipv6"),
147 s_masklen("masklen"),
148 s_chain("chain"),
149 s_flags("flags"),
150 s_services("services"),
151 s_regex("regex"),
152 s_replacement("replacement"),
153 s_class("class"),
154 s_ttl("ttl"),
155 s_A("A"),
156 s_MX("MX"),
157 s_CNAME("CNAME"),
158 s_NS("NS"),
159 s_PTR("PTR"),
160 s_HINFO("HINFO"),
161 s_TXT("TXT"),
162 s_SOA("SOA"),
163 s_AAAA("AAAA"),
164 s_A6("A6"),
165 s_SRV("SRV"),
166 s_NAPTR("NAPTR"),
167 s_IN("IN");
169 #define CHECKCP(n) do { \
170 if ((cp + (n)) > end) { \
171 return nullptr; \
173 } while (0)
175 static unsigned char *php_parserr(unsigned char *cp, unsigned char* end,
176 querybuf *answer,
177 int type_to_fetch, bool store,
178 Array &subarray) {
179 unsigned short type, cls ATTRIBUTE_UNUSED, dlen;
180 unsigned long ttl;
181 int64_t n, i;
182 unsigned short s;
183 unsigned char *tp, *p;
184 char name[255 + 2]; // IETF STD 13 section 3.1; 255 bytes
185 int have_v6_break = 0, in_v6_break = 0;
187 n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, sizeof(name) - 2);
188 if (n < 0) {
189 return NULL;
191 cp += n;
193 CHECKCP(10);
194 GETSHORT(type, cp);
195 GETSHORT(cls, cp);
196 GETLONG(ttl, cp);
197 GETSHORT(dlen, cp);
198 CHECKCP(dlen);
199 if (type_to_fetch != T_ANY && type != type_to_fetch) {
200 cp += dlen;
201 return cp;
204 if (!store) {
205 cp += dlen;
206 return cp;
209 subarray.set(s_host, String(name, CopyString));
210 switch (type) {
211 case DNS_T_A:
212 CHECKCP(4);
213 subarray.set(s_type, s_A);
214 snprintf(name, sizeof(name), "%d.%d.%d.%d", cp[0], cp[1], cp[2], cp[3]);
215 subarray.set(s_ip, String(name, CopyString));
216 cp += dlen;
217 break;
218 case DNS_T_MX:
219 CHECKCP(2);
220 subarray.set(s_type, s_MX);
221 GETSHORT(n, cp);
222 subarray.set(s_pri, n);
223 /* no break; */
224 case DNS_T_CNAME:
225 if (type == DNS_T_CNAME) {
226 subarray.set(s_type, s_CNAME);
228 /* no break; */
229 case DNS_T_NS:
230 if (type == DNS_T_NS) {
231 subarray.set(s_type, s_NS);
233 /* no break; */
234 case DNS_T_PTR:
235 if (type == DNS_T_PTR) {
236 subarray.set(s_type, s_PTR);
238 n = dn_expand(answer->qb2, answer->qb2+65536, cp, name, (sizeof name) - 2);
239 if (n < 0) {
240 return NULL;
242 cp += n;
243 subarray.set(s_target, String(name, CopyString));
244 break;
245 case DNS_T_HINFO:
246 /* See RFC 1010 for values */
247 subarray.set(s_type, s_HINFO);
248 CHECKCP(1);
249 n = *cp & 0xFF;
250 cp++;
251 CHECKCP(n);
252 subarray.set(s_cpu, String((const char *)cp, n, CopyString));
253 cp += n;
254 CHECKCP(1);
255 n = *cp & 0xFF;
256 cp++;
257 CHECKCP(n);
258 subarray.set(s_os, String((const char *)cp, n, CopyString));
259 cp += n;
260 break;
261 case DNS_T_TXT: {
262 int l1 = 0, l2 = 0;
264 String s = String(dlen, ReserveString);
265 tp = (unsigned char *)s.mutableData();
267 while (l1 < dlen) {
268 n = cp[l1];
269 if ((n + l1) > dlen) {
270 // bad record, don't set anything
271 break;
273 memcpy(tp + l1 , cp + l1 + 1, n);
274 l1 = l1 + n + 1;
275 l2 = l2 + n;
277 s.setSize(l2);
278 cp += dlen;
280 subarray.set(s_type, s_TXT);
281 subarray.set(s_txt, s);
282 break;
284 case DNS_T_SOA:
285 subarray.set(s_type, s_SOA);
286 n = dn_expand(answer->qb2, end, cp, name, (sizeof name) -2);
287 if (n < 0) {
288 return NULL;
290 cp += n;
291 subarray.set(s_mname, String(name, CopyString));
292 n = dn_expand(answer->qb2, end, cp, name, (sizeof name) -2);
293 if (n < 0) {
294 return NULL;
296 cp += n;
297 subarray.set(s_rname, String(name, CopyString));
298 CHECKCP(5*4);
299 GETLONG(n, cp);
300 subarray.set(s_serial, n);
301 GETLONG(n, cp);
302 subarray.set(s_refresh, n);
303 GETLONG(n, cp);
304 subarray.set(s_retry, n);
305 GETLONG(n, cp);
306 subarray.set(s_expire, n);
307 GETLONG(n, cp);
308 subarray.set(s_minimum_ttl, n);
309 break;
310 case DNS_T_AAAA:
311 tp = (unsigned char *)name;
312 CHECKCP(8*2);
313 for (i = 0; i < 8; i++) {
314 GETSHORT(s, cp);
315 if (s != 0) {
316 if (tp > (u_char *)name) {
317 in_v6_break = 0;
318 tp[0] = ':';
319 tp++;
321 tp += sprintf((char *)tp, "%x", s);
322 } else {
323 if (!have_v6_break) {
324 have_v6_break = 1;
325 in_v6_break = 1;
326 tp[0] = ':';
327 tp++;
328 } else if (!in_v6_break) {
329 tp[0] = ':';
330 tp++;
331 tp[0] = '0';
332 tp++;
336 if (have_v6_break && in_v6_break) {
337 tp[0] = ':';
338 tp++;
340 tp[0] = '\0';
341 subarray.set(s_type, s_AAAA);
342 subarray.set(s_ipv6, String(name, CopyString));
343 break;
344 case DNS_T_A6:
345 p = cp;
346 subarray.set(s_type, s_A6);
347 CHECKCP(1);
348 n = ((int)cp[0]) & 0xFF;
349 cp++;
350 subarray.set(s_masklen, n);
351 tp = (unsigned char *)name;
352 if (n > 15) {
353 have_v6_break = 1;
354 in_v6_break = 1;
355 tp[0] = ':';
356 tp++;
358 if (n % 16 > 8) {
359 /* Partial short */
360 if (cp[0] != 0) {
361 if (tp > (u_char *)name) {
362 in_v6_break = 0;
363 tp[0] = ':';
364 tp++;
366 sprintf((char *)tp, "%x", cp[0] & 0xFF);
367 } else {
368 if (!have_v6_break) {
369 have_v6_break = 1;
370 in_v6_break = 1;
371 tp[0] = ':';
372 tp++;
373 } else if (!in_v6_break) {
374 tp[0] = ':';
375 tp++;
376 tp[0] = '0';
377 tp++;
380 cp++;
382 for (i = (n + 8)/16; i < 8; i++) {
383 CHECKCP(2);
384 GETSHORT(s, cp);
385 if (s != 0) {
386 if (tp > (u_char *)name) {
387 in_v6_break = 0;
388 tp[0] = ':';
389 tp++;
391 tp += sprintf((char*)tp,"%x",s);
392 } else {
393 if (!have_v6_break) {
394 have_v6_break = 1;
395 in_v6_break = 1;
396 tp[0] = ':';
397 tp++;
398 } else if (!in_v6_break) {
399 tp[0] = ':';
400 tp++;
401 tp[0] = '0';
402 tp++;
406 if (have_v6_break && in_v6_break) {
407 tp[0] = ':';
408 tp++;
410 tp[0] = '\0';
411 subarray.set(s_ipv6, String(name, CopyString));
412 if (cp < p + dlen) {
413 n = dn_expand(answer->qb2, end, cp, name,
414 (sizeof name) - 2);
415 if (n < 0) {
416 return NULL;
418 cp += n;
419 subarray.set(s_chain, String(name, CopyString));
421 break;
422 case DNS_T_SRV:
423 CHECKCP(3*2);
424 subarray.set(s_type, s_SRV);
425 GETSHORT(n, cp);
426 subarray.set(s_pri, n);
427 GETSHORT(n, cp);
428 subarray.set(s_weight, n);
429 GETSHORT(n, cp);
430 subarray.set(s_port, n);
431 n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
432 if (n < 0) {
433 return NULL;
435 cp += n;
436 subarray.set(s_target, String(name, CopyString));
437 break;
438 case DNS_T_NAPTR:
439 CHECKCP(2*2);
440 subarray.set(s_type, s_NAPTR);
441 GETSHORT(n, cp);
442 subarray.set(s_order, n);
443 GETSHORT(n, cp);
444 subarray.set(s_pref, n);
446 CHECKCP(1);
447 n = (cp[0] & 0xFF);
448 ++cp;
449 CHECKCP(n);
450 subarray.set(s_flags, String((const char *)cp, n, CopyString));
451 cp += n;
453 CHECKCP(1);
454 n = (cp[0] & 0xFF);
455 ++cp;
456 CHECKCP(n);
457 subarray.set(s_services, String((const char *)cp, n, CopyString));
458 cp += n;
460 CHECKCP(1);
461 n = (cp[0] & 0xFF);
462 ++cp;
463 CHECKCP(n);
464 subarray.set(s_regex, String((const char *)cp, n, CopyString));
465 cp += n;
467 n = dn_expand(answer->qb2, end, cp, name, (sizeof name) - 2);
468 if (n < 0) {
469 return NULL;
471 cp += n;
472 subarray.set(s_replacement, String(name, CopyString));
473 break;
474 default:
475 cp += dlen;
478 subarray.set(s_class, s_IN);
479 subarray.set(s_ttl, (int)ttl);
480 return cp;
483 Variant HHVM_FUNCTION(dns_get_record, const String& hostname, int type /*= -1*/,
484 VRefParam authnsRef /* = null */,
485 VRefParam addtlRef /* = null */) {
486 IOStatusHelper io("dns_get_record", hostname.data(), type);
487 if (type < 0) type = PHP_DNS_ALL;
488 if (type & ~PHP_DNS_ALL && type != PHP_DNS_ANY) {
489 raise_warning("Type '%d' not supported", type);
490 return false;
493 unsigned char *cp = NULL, *end = NULL;
494 int qd, an, ns = 0, ar = 0;
495 querybuf answer;
497 /* - We emulate an or'ed type mask by querying type by type.
498 * (Steps 0 - NUMTYPES-1 )
499 * If additional info is wanted we check again with DNS_T_ANY
500 * (step NUMTYPES / NUMTYPES+1 )
501 * store_results is used to skip storing the results retrieved in step
502 * NUMTYPES+1 when results were already fetched.
503 * - In case of PHP_DNS_ANY we use the directly fetch DNS_T_ANY.
504 * (step NUMTYPES+1 )
506 Array ret = Array::Create();
507 bool first_query = true;
508 bool store_results = true;
509 for (int t = (type == PHP_DNS_ANY ? (PHP_DNS_NUM_TYPES + 1) : 0);
510 t < PHP_DNS_NUM_TYPES + 2 || first_query; t++) {
511 first_query = false;
512 int type_to_fetch;
513 switch (t) {
514 case 0: type_to_fetch = type & PHP_DNS_A ? DNS_T_A : 0; break;
515 case 1: type_to_fetch = type & PHP_DNS_NS ? DNS_T_NS : 0; break;
516 case 2: type_to_fetch = type & PHP_DNS_CNAME ? DNS_T_CNAME : 0; break;
517 case 3: type_to_fetch = type & PHP_DNS_SOA ? DNS_T_SOA : 0; break;
518 case 4: type_to_fetch = type & PHP_DNS_PTR ? DNS_T_PTR : 0; break;
519 case 5: type_to_fetch = type & PHP_DNS_HINFO ? DNS_T_HINFO : 0; break;
520 case 6: type_to_fetch = type & PHP_DNS_MX ? DNS_T_MX : 0; break;
521 case 7: type_to_fetch = type & PHP_DNS_TXT ? DNS_T_TXT : 0; break;
522 case 8: type_to_fetch = type & PHP_DNS_AAAA ? DNS_T_AAAA : 0; break;
523 case 9: type_to_fetch = type & PHP_DNS_SRV ? DNS_T_SRV : 0; break;
524 case 10: type_to_fetch = type & PHP_DNS_NAPTR ? DNS_T_NAPTR : 0; break;
525 case 11: type_to_fetch = type & PHP_DNS_A6 ? DNS_T_A6 : 0; break;
526 case PHP_DNS_NUM_TYPES:
527 store_results = false;
528 continue;
529 default:
530 case (PHP_DNS_NUM_TYPES + 1):
531 type_to_fetch = DNS_T_ANY;
532 break;
534 if (!type_to_fetch) continue;
536 struct __res_state *res;
537 res = ResolverInit::s_res.get()->getResolver();
538 if (res == NULL) {
539 return false;
542 int n = res_nsearch(res, hostname.data(), C_IN, type_to_fetch,
543 answer.qb2, sizeof answer);
544 if (n < 0) {
545 res_nclose(res);
546 php_dns_free_res(res);
547 continue;
550 HEADER *hp;
551 cp = answer.qb2 + HFIXEDSZ;
552 end = answer.qb2 + n;
553 hp = (HEADER *)&answer;
554 qd = ntohs(hp->qdcount);
555 an = ntohs(hp->ancount);
556 ns = ntohs(hp->nscount);
557 ar = ntohs(hp->arcount);
559 /* Skip QD entries, they're only used by dn_expand later on */
560 while (qd-- > 0) {
561 n = dn_skipname(cp, end);
562 if (n < 0) {
563 raise_warning("Unable to parse DNS data received");
564 res_nclose(res);
565 php_dns_free_res(res);
566 return false;
568 cp += n + QFIXEDSZ;
571 /* YAY! Our real answers! */
572 while (an-- && cp && cp < end) {
573 Array retval;
574 cp = php_parserr(cp, end, &answer, type_to_fetch, store_results, retval);
575 if (!retval.empty() && store_results) {
576 ret.append(retval);
579 res_nclose(res);
580 php_dns_free_res(res);
583 Array authns;
584 Array addtl;
586 /* List of Authoritative Name Servers */
587 while (ns-- > 0 && cp && cp < end) {
588 Array retval;
589 cp = php_parserr(cp, end, &answer, DNS_T_ANY, true, retval);
590 if (!retval.empty()) {
591 authns.append(retval);
595 /* Additional records associated with authoritative name servers */
596 while (ar-- > 0 && cp && cp < end) {
597 Array retval;
598 cp = php_parserr(cp, end, &answer, DNS_T_ANY, true, retval);
599 if (!retval.empty()) {
600 addtl.append(retval);
604 authnsRef.assignIfRef(authns);
605 addtlRef.assignIfRef(addtl);
606 return ret;
609 bool HHVM_FUNCTION(getmxrr, const String& hostname,
610 VRefParam mxhostsRef,
611 VRefParam weightsRef /* = null */) {
612 IOStatusHelper io("dns_get_mx", hostname.data());
613 int count, qdc;
614 unsigned short type, weight;
615 unsigned char ans[MAXPACKET];
616 char buf[255 + 1]; // IETF STD 13 section 3.1; 255 bytes
617 unsigned char *cp, *end;
619 Array mxhosts;
620 Array weights;
621 SCOPE_EXIT {
622 mxhostsRef.assignIfRef(mxhosts);
623 weightsRef.assignIfRef(weights);
626 /* Go! */
627 struct __res_state *res;
628 res = ResolverInit::s_res.get()->getResolver();
629 if (res == NULL) {
630 return false;
633 int i = res_nsearch(res, hostname.data(), C_IN, DNS_T_MX,
634 (unsigned char*)&ans, sizeof(ans));
635 if (i < 0) {
636 res_nclose(res);
637 php_dns_free_res(res);
638 return false;
640 if (i > (int)sizeof(ans)) {
641 i = sizeof(ans);
643 HEADER *hp = (HEADER *)&ans;
644 cp = (unsigned char *)&ans + HFIXEDSZ;
645 end = (unsigned char *)&ans +i;
646 for (qdc = ntohs((unsigned short)hp->qdcount); qdc--; cp += i + QFIXEDSZ) {
647 if ((i = dn_skipname(cp, end)) < 0 ) {
648 res_nclose(res);
649 php_dns_free_res(res);
650 return false;
653 count = ntohs((unsigned short)hp->ancount);
654 while (--count >= 0 && cp < end) {
655 if ((i = dn_skipname(cp, end)) < 0 ) {
656 res_nclose(res);
657 php_dns_free_res(res);
658 return false;
660 cp += i;
661 GETSHORT(type, cp);
662 cp += INT16SZ + INT32SZ;
663 GETSHORT(i, cp);
664 if (type != DNS_T_MX) {
665 cp += i;
666 continue;
668 GETSHORT(weight, cp);
669 if ((i = dn_expand(ans, end, cp, buf, sizeof(buf)-1)) < 0) {
670 res_nclose(res);
671 php_dns_free_res(res);
672 return false;
674 cp += i;
675 mxhosts.append(String(buf, CopyString));
676 weights.append(weight);
678 res_nclose(res);
679 php_dns_free_res(res);
680 return true;
683 ///////////////////////////////////////////////////////////////////////////////
685 #endif