1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Authors: Sara Golemon <pollita@php.net>                              |
16    +----------------------------------------------------------------------+
17  */
18 
19 #include "php.h"
20 #include "php_network.h"
21 
22 #if HAVE_ARPA_INET_H
23 # include <arpa/inet.h>
24 #endif
25 
26 #if HAVE_NET_IF_H
27 # include <net/if.h>
28 #endif
29 
30 #if HAVE_GETIFADDRS
31 # include <ifaddrs.h>
32 #endif
33 
34 #ifdef PHP_WIN32
35 # ifndef __clang__
36 # include <intrin.h>
37 # endif
38 # include <winsock2.h>
39 # include <ws2ipdef.h>
40 # include <Ws2tcpip.h>
41 # include <iphlpapi.h>
42 #else
43 # include <netdb.h>
44 #endif
45 
php_inet_ntop(const struct sockaddr *addr)46 PHPAPI zend_string* php_inet_ntop(const struct sockaddr *addr) {
47 	socklen_t addrlen = sizeof(struct sockaddr_in);
48 
49 	if (!addr) { return NULL; }
50 
51 	/* Prefer inet_ntop() as it's more task-specific and doesn't have to be demangled */
52 #if HAVE_INET_NTOP
53 	switch (addr->sa_family) {
54 #ifdef AF_INET6
55 		case AF_INET6: {
56 			zend_string *ret = zend_string_alloc(INET6_ADDRSTRLEN, 0);
57 			if (inet_ntop(AF_INET6, &(((struct sockaddr_in6*)addr)->sin6_addr), ZSTR_VAL(ret), INET6_ADDRSTRLEN)) {
58 				ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret));
59 				return ret;
60 			}
61 			zend_string_efree(ret);
62 			break;
63 		}
64 #endif
65 		case AF_INET: {
66 			zend_string *ret = zend_string_alloc(INET_ADDRSTRLEN, 0);
67 			if (inet_ntop(AF_INET, &(((struct sockaddr_in*)addr)->sin_addr), ZSTR_VAL(ret), INET_ADDRSTRLEN)) {
68 				ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret));
69 				return ret;
70 			}
71 			zend_string_efree(ret);
72 			break;
73 		}
74 	}
75 #endif
76 
77 	/* Fallback on getnameinfo() */
78 	switch (addr->sa_family) {
79 #ifdef AF_INET6
80 		case AF_INET6:
81 			addrlen = sizeof(struct sockaddr_in6);
82 			/* fallthrough */
83 #endif
84 		case AF_INET: {
85 			zend_string *ret = zend_string_alloc(NI_MAXHOST, 0);
86 			if (getnameinfo(addr, addrlen, ZSTR_VAL(ret), NI_MAXHOST, NULL, 0, NI_NUMERICHOST) == SUCCESS) {
87 				/* Also demangle numeric host with %name suffix */
88 				char *colon = strchr(ZSTR_VAL(ret), '%');
89 				if (colon) { *colon = 0; }
90 				ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret));
91 				return ret;
92 			}
93 			zend_string_efree(ret);
94 			break;
95 		}
96 	}
97 
98 	return NULL;
99 }
100 
101 #if defined(PHP_WIN32) || HAVE_GETIFADDRS
iface_append_unicast(zval *unicast, zend_long flags, struct sockaddr *addr, struct sockaddr *netmask, struct sockaddr *broadcast, struct sockaddr *ptp)102 static void iface_append_unicast(zval *unicast, zend_long flags,
103                                  struct sockaddr *addr, struct sockaddr *netmask,
104                                  struct sockaddr *broadcast, struct sockaddr *ptp) {
105 	zend_string *host;
106 	zval u;
107 
108 	array_init(&u);
109 	add_assoc_long(&u, "flags", flags);
110 
111 	if (addr) {
112 		add_assoc_long(&u, "family", addr->sa_family);
113 		if ((host = php_inet_ntop(addr))) {
114 			add_assoc_str(&u, "address", host);
115 		}
116 	}
117 	if ((host = php_inet_ntop(netmask))) {
118 		add_assoc_str(&u, "netmask", host);
119 	}
120 
121 	if ((host = php_inet_ntop(broadcast))) {
122 		add_assoc_str(&u, "broadcast", host);
123 	}
124 
125 	if ((host = php_inet_ntop(ptp))) {
126 		add_assoc_str(&u, "ptp", host);
127 	}
128 
129 	add_next_index_zval(unicast, &u);
130 }
131 #endif
132 
133 /* {{{ proto array|false net_get_interfaces()
134 Returns an array in the form:
135 array(
136   'ifacename' => array(
137     'description' => 'Awesome interface', // Win32 only
138     'mac' => '00:11:22:33:44:55',         // Win32 only
139     'mtu' => 1234,                        // Win32 only
140     'unicast' => array(
141       0 => array(
142         'family' => 2,                    // e.g. AF_INET, AF_INET6, AF_PACKET
143         'address' => '127.0.0.1',
144         'netmnask' => '255.0.0.0',
145         'broadcast' => '127.255.255.255', // POSIX only
146         'ptp' => '127.0.0.2',             // POSIX only
147       ), // etc...
148     ),
149   ), // etc...
150 )
151 */
PHP_FUNCTIONnull152 PHP_FUNCTION(net_get_interfaces) {
153 #ifdef PHP_WIN32
154 # define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
155 # define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
156 	ULONG family = AF_UNSPEC;
157 	ULONG flags = GAA_FLAG_INCLUDE_PREFIX;
158 	PIP_ADAPTER_ADDRESSES pAddresses = NULL, p;
159 	PIP_ADAPTER_UNICAST_ADDRESS u = NULL;
160 	ULONG outBufLen = 0;
161 	DWORD dwRetVal = 0;
162 
163 	ZEND_PARSE_PARAMETERS_NONE();
164 
165 	// Make an initial call to GetAdaptersAddresses to get the
166 	// size needed into the outBufLen variable
167 	if (GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen) == ERROR_BUFFER_OVERFLOW) {
168 		FREE(pAddresses);
169 		pAddresses = (IP_ADAPTER_ADDRESSES *) MALLOC(outBufLen);
170 	}
171 
172 	if (pAddresses == NULL) {
173 		zend_error(E_WARNING, "Memory allocation failed for IP_ADAPTER_ADDRESSES struct");
174 		RETURN_FALSE;
175 	}
176 
177 	dwRetVal = GetAdaptersAddresses(family, flags, NULL, pAddresses, &outBufLen);
178 
179 	if (NO_ERROR != dwRetVal) {
180 		char *buf = php_win32_error_to_msg(GetLastError());
181 		zend_error(E_WARNING, "GetAdaptersAddresses failed: %s", buf);
182 		php_win32_error_msg_free(buf);
183 		FREE(pAddresses);
184 		RETURN_FALSE;
185 	}
186 
187 	array_init(return_value);
188 	for (p = pAddresses; p; p = p->Next) {
189 		zval iface, unicast;
190 
191 		if ((IF_TYPE_ETHERNET_CSMACD != p->IfType) && (IF_TYPE_SOFTWARE_LOOPBACK != p->IfType)) {
192 			continue;
193 		}
194 
195 		array_init(&iface);
196 
197 		if (p->Description) {
198 			char tmp[256];
199 			memset(tmp, 0, sizeof(tmp));
200 			wcstombs(tmp, p->Description, sizeof(tmp));
201 			add_assoc_string(&iface, "description", tmp);
202 		}
203 
204 		if (p->PhysicalAddressLength > 0) {
205 			zend_string *mac = zend_string_alloc(p->PhysicalAddressLength * 3, 0);
206 			char *s = ZSTR_VAL(mac);
207 			ULONG i;
208 			for (i = 0; i < p->PhysicalAddressLength; ++i) {
209 				s += snprintf(s, 4, "%02X:", p->PhysicalAddress[i]);
210 			}
211 			*(--s) = 0;
212 			ZSTR_LEN(mac) = s - ZSTR_VAL(mac);
213 			add_assoc_str(&iface, "mac", mac);
214 		}
215 
216 		/* Flags could be placed at this level,
217 		 * but we repeat it in the unicast subarray
218 		 * for consistency with the POSIX version.
219 		 */
220 		add_assoc_long(&iface, "mtu", p->Mtu);
221 
222 		array_init(&unicast);
223 		for (u = p->FirstUnicastAddress; u; u = u->Next) {
224 			switch (u->Address.lpSockaddr->sa_family) {
225 				case AF_INET: {
226 					ULONG mask;
227 					struct sockaddr_in sin_mask;
228 
229 					ConvertLengthToIpv4Mask(u->OnLinkPrefixLength, &mask);
230 					sin_mask.sin_family = AF_INET;
231 					sin_mask.sin_addr.s_addr = mask;
232 
233 					iface_append_unicast(&unicast, p->Flags,
234 					                     (struct sockaddr*)u->Address.lpSockaddr,
235 					                     (struct sockaddr*)&sin_mask, NULL, NULL);
236 					break;
237 				}
238 				case AF_INET6: {
239 					ULONG i, j;
240 					struct sockaddr_in6 sin6_mask;
241 
242 					memset(&sin6_mask, 0, sizeof(sin6_mask));
243 					sin6_mask.sin6_family = AF_INET6;
244 					for (i = u->OnLinkPrefixLength, j = 0; i > 0; i -= 8, ++j) {
245 						sin6_mask.sin6_addr.s6_addr[j] = (i >= 8) ? 0xff : ((ULONG)((0xffU << (8 - i)) & 0xffU));
246 					}
247 
248 					iface_append_unicast(&unicast, p->Flags,
249 					                     (struct sockaddr*)u->Address.lpSockaddr,
250 										 (struct sockaddr*)&sin6_mask, NULL, NULL);
251 					break;
252 				}
253 			}
254 		}
255 		add_assoc_zval(&iface, "unicast", &unicast);
256 
257 		add_assoc_bool(&iface, "up", (p->OperStatus == IfOperStatusUp));
258 
259 		add_assoc_zval(return_value, p->AdapterName, &iface);
260 	}
261 
262 	FREE(pAddresses);
263 #undef MALLOC
264 #undef FREE
265 #elif HAVE_GETIFADDRS /* !PHP_WIN32 */
266 	struct ifaddrs *addrs = NULL, *p;
267 
268 	ZEND_PARSE_PARAMETERS_NONE();
269 
270 	if (getifaddrs(&addrs)) {
271 		php_error(E_WARNING, "getifaddrs() failed %d: %s", errno, strerror(errno));
272 		RETURN_FALSE;
273 	}
274 
275 	array_init(return_value);
276 	for (p = addrs; p; p = p->ifa_next) {
277 		zval *iface = zend_hash_str_find(Z_ARR_P(return_value), p->ifa_name, strlen(p->ifa_name));
278 		zval *unicast, *status;
279 
280 		if (!iface) {
281 			zval newif;
282 			array_init(&newif);
283 			iface = zend_hash_str_add(Z_ARR_P(return_value), p->ifa_name, strlen(p->ifa_name), &newif);
284 		}
285 
286 		unicast = zend_hash_str_find(Z_ARR_P(iface), "unicast", sizeof("unicast") - 1);
287 		if (!unicast) {
288 			zval newuni;
289 			array_init(&newuni);
290 			unicast = zend_hash_str_add(Z_ARR_P(iface), "unicast", sizeof("unicast") - 1, &newuni);
291 		}
292 
293 		iface_append_unicast(unicast,
294 		                     p->ifa_flags,
295 		                     p->ifa_addr, p->ifa_netmask,
296 		                     (p->ifa_flags & IFF_BROADCAST) ? p->ifa_broadaddr : NULL,
297 					         (p->ifa_flags & IFF_POINTOPOINT) ? p->ifa_dstaddr : NULL);
298 		status = zend_hash_str_find(Z_ARR_P(iface), "up", sizeof("up") - 1);
299 		if (!status) {
300 			add_assoc_bool(iface, "up", ((p->ifa_flags & IFF_UP) != 0));
301 		}
302 	}
303 
304 	freeifaddrs(addrs);
305 #else
306 	/* Should never happen as we never register the function */
307 	php_error(E_WARNING, "No support for net_get_interfaces");
308 	RETURN_FALSE;
309 #endif
310 }
311 /* }}} */
312