xref: /illumos-gate/usr/src/lib/libsocket/inet/getnameinfo.c (revision 8a2b682e57a046b828f37bcde1776f131ef4629f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 #include <sys/types.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <strings.h>
32 #include <stdio.h>
33 #include <ctype.h>
34 #include <sys/socket.h>
35 #include <netdb.h>
36 #include <arpa/inet.h>
37 #include <nss_dbdefs.h>
38 #include <netinet/in.h>
39 #include <sys/socket.h>
40 #include <net/if.h>
41 
42 #define	sa2sin(x)	((struct sockaddr_in *)(x))
43 #define	sa2sin6(x)	((struct sockaddr_in6 *)(x))
44 
45 #define	NI_MASK	(NI_NOFQDN | NI_NUMERICHOST | NI_NAMEREQD | NI_NUMERICSERV | \
46     NI_DGRAM | NI_WITHSCOPEID)
47 
48 static int addzoneid(const struct sockaddr_in6 *sa, char *host,
49     size_t hostlen);
50 static size_t getzonestr(const struct sockaddr_in6 *sa, char *zonestr,
51     size_t zonelen);
52 static const char *_inet_ntop_native();
53 /*
54  * getnameinfo:
55  *
56  * Purpose:
57  *   Routine for performing Address-to-nodename in a
58  *   protocol-independent fashion.
59  * Description:
60  *   This function looks up an IP address and port number provided
61  *   by the caller in the name service database and returns the nodename
62  *   and servname respectively in the buffers provided by the caller.
63  * Input Parameters:
64  *   sa      - points to either a sockaddr_in structure (for
65  *             IPv4) or a sockaddr_in6 structure (for IPv6).
66  *   salen   - length of the sockaddr_in or sockaddr_in6 structure.
67  *   hostlen - length of caller supplied "host" buffer
68  *   servlen - length of caller supplied "serv" buffer
69  *   flags   - changes default actions based on setting.
70  *       Possible settings for "flags":
71  *       NI_NOFQDN - Always return nodename portion of the fully-qualified
72  *                   domain name (FQDN).
73  *       NI_NUMERICHOST - Always return numeric form of the host's
74  *			  address.
75  *       NI_NAMEREQD - If hostname cannot be located in database,
76  *                     don't return numeric form of address - return
77  *                     an error instead.
78  *       NI_NUMERICSERV - Always return numeric form of the service address
79  *                        instead of its name.
80  *       NI_DGRAM - Specifies that the service is a datagram service, and
81  *                  causes getservbyport() to be called with a second
82  *                  argument of "udp" instead of its default "tcp".
83  * Output Parameters:
84  *   host - return the nodename associcated with the IP address in the
85  *          buffer pointed to by the "host" argument.
86  *   serv - return the service name associated with the port number
87  *          in the buffer pointed to by the "serv" argument.
88  * Return Value:
89  *   This function indicates successful completion by a zero return
90  *   value; a non-zero return value indicates failure.
91  */
92 int
93 getnameinfo(const struct sockaddr *sa, socklen_t salen,
94     char *host, socklen_t hostlen,
95     char *serv, socklen_t servlen, int flags)
96 {
97 	char		*addr;
98 	size_t		alen, slen;
99 	in_port_t	port;
100 	int		errnum;
101 	int		err;
102 
103 	/* Verify correctness of buffer lengths */
104 	if ((hostlen == 0) && (servlen == 0))
105 		return (EAI_FAIL);
106 	/* Verify correctness of possible flag settings */
107 	if ((flags != 0) && (flags & ~NI_MASK))
108 		return (EAI_BADFLAGS);
109 	if (sa == NULL)
110 		return (EAI_ADDRFAMILY);
111 	switch (sa->sa_family) {
112 	case AF_INET:
113 		addr = (char *)&sa2sin(sa)->sin_addr;
114 		alen = sizeof (struct in_addr);
115 		slen = sizeof (struct sockaddr_in);
116 		port = (sa2sin(sa)->sin_port); /* network byte order */
117 		break;
118 	case AF_INET6:
119 		addr = (char *)&sa2sin6(sa)->sin6_addr;
120 		alen = sizeof (struct in6_addr);
121 		slen = sizeof (struct sockaddr_in6);
122 		port = (sa2sin6(sa)->sin6_port); /* network byte order */
123 		break;
124 	default:
125 		return (EAI_FAMILY);
126 	}
127 	if (salen != slen)
128 		return (EAI_FAIL);
129 	/*
130 	 * Case 1: if Caller sets hostlen != 0, then
131 	 * fill in "host" buffer that user passed in
132 	 * with appropriate text string.
133 	 */
134 	if (hostlen != 0) {
135 		if (flags & NI_NUMERICHOST) {
136 			/* Caller wants the host's numeric address */
137 			if (inet_ntop(sa->sa_family, addr,
138 			    host, hostlen) == NULL)
139 				return (EAI_SYSTEM);
140 		} else {
141 			struct hostent	*hp;
142 
143 			/* Caller wants the name of host */
144 			hp = getipnodebyaddr(addr, alen, sa->sa_family,
145 			    &errnum);
146 			if (hp != NULL) {
147 				if (flags & NI_NOFQDN) {
148 					char *dot;
149 					/*
150 					 * Caller doesn't want fully-qualified
151 					 * name.
152 					 */
153 					dot = strchr(hp->h_name, '.');
154 					if (dot != NULL)
155 						*dot = '\0';
156 				}
157 				if (strlen(hp->h_name) + 1 > hostlen) {
158 					freehostent(hp);
159 					return (EAI_OVERFLOW);
160 				}
161 				(void) strcpy(host, hp->h_name);
162 				freehostent(hp);
163 			} else {
164 				/*
165 				 * Host's name cannot be located in the name
166 				 * service database. If NI_NAMEREQD is set,
167 				 * return error; otherwise, return host's
168 				 * numeric address.
169 				 */
170 				if (flags & NI_NAMEREQD) {
171 					switch (errnum) {
172 					case HOST_NOT_FOUND:
173 						return (EAI_NONAME);
174 					case TRY_AGAIN:
175 						return (EAI_AGAIN);
176 					case NO_RECOVERY:
177 						return (EAI_FAIL);
178 					case NO_ADDRESS:
179 						return (EAI_NODATA);
180 					default:
181 						return (EAI_SYSTEM);
182 					}
183 				}
184 				if (_inet_ntop_native(sa->sa_family, addr,
185 				    host, hostlen) == NULL)
186 					return (EAI_SYSTEM);
187 			}
188 		}
189 
190 		/*
191 		 * Check for a non-zero sin6_scope_id, indicating a
192 		 * zone-id needs to be appended to the resultant 'host'
193 		 * string.
194 		 */
195 		if ((sa->sa_family == AF_INET6) &&
196 		    (sa2sin6(sa)->sin6_scope_id != 0)) {
197 			/*
198 			 * According to draft-ietf-ipngwg-scoping-arch-XX, only
199 			 * non-global scope addresses can make use of the
200 			 * <addr>%<zoneid> format.  This implemenation
201 			 * supports only link scope addresses, since the use of
202 			 * site-local addressing is not yet fully specified.
203 			 * If the address meets this criteria, attempt to add a
204 			 * zone-id to 'host'.  If it does not, return
205 			 * EAI_NONAME.
206 			 */
207 			if (IN6_IS_ADDR_LINKSCOPE(&(sa2sin6(sa)->sin6_addr))) {
208 				if ((err = addzoneid(sa2sin6(sa), host,
209 				    hostlen)) != 0) {
210 					return (err);
211 				}
212 			} else {
213 				return (EAI_NONAME);
214 			}
215 		}
216 	}
217 	/*
218 	 * Case 2: if Caller sets servlen != 0, then
219 	 * fill in "serv" buffer that user passed in
220 	 * with appropriate text string.
221 	 */
222 	if (servlen != 0) {
223 		char port_buf[10];
224 		int portlen;
225 
226 		if (flags & NI_NUMERICSERV) {
227 			/* Caller wants the textual form of the port number */
228 			portlen = snprintf(port_buf, sizeof (port_buf), "%hu",
229 			    ntohs(port));
230 			if (servlen < portlen + 1)
231 				return (EAI_OVERFLOW);
232 			(void) strcpy(serv, port_buf);
233 		} else {
234 			struct servent	*sp;
235 			/*
236 			 * Caller wants the name of the service.
237 			 * If NI_DGRAM is set, get service name for
238 			 * specified port for udp.
239 			 */
240 			sp = getservbyport(port,
241 			    flags & NI_DGRAM ? "udp" : "tcp");
242 			if (sp != NULL) {
243 				if (servlen < strlen(sp->s_name) + 1)
244 					return (EAI_OVERFLOW);
245 				(void) strcpy(serv, sp->s_name);
246 			} else {
247 				/*
248 				 * if service is not in the name server's
249 				 * database, fill buffer with numeric form for
250 				 * port number.
251 				 */
252 				portlen = snprintf(port_buf, sizeof (port_buf),
253 				    "%hu", ntohs(port));
254 				if (servlen < portlen + 1)
255 					return (EAI_OVERFLOW);
256 				(void) strcpy(serv, port_buf);
257 			}
258 		}
259 	}
260 	return (0);
261 }
262 
263 /*
264  * addzoneid(sa, host, hostlen)
265  *
266  * Appends a zone-id to the input 'host' string if the input sin6_scope_id
267  * is non-zero.  The resultant 'host' string would be of the form
268  * 'host'%'zone-id'.  Where 'zone-id' can be either an interface name or a
269  * literal interface index.
270  *
271  * Return Values:
272  * 0 - on success
273  * EAI_MEMORY - an error occured when forming the output string
274  */
275 static int
276 addzoneid(const struct sockaddr_in6 *sa, char *host, size_t hostlen)
277 {
278 	char zonestr[LIFNAMSIZ];
279 	size_t zonelen;
280 	size_t addrlen = strlen(host);
281 
282 	/* make sure zonelen is valid sizeof (<addr>%<zoneid>\0) */
283 	if (((zonelen = getzonestr(sa, zonestr, sizeof (zonestr))) == 0) ||
284 	    ((addrlen + 1 + zonelen + 1) > hostlen)) {
285 		return (EAI_MEMORY);
286 	}
287 
288 	/* Create address string of form <addr>%<zoneid> */
289 	host[addrlen] = '%'; /* place address-zoneid delimiter */
290 	(void) strlcpy((host + addrlen + 1), zonestr, (zonelen + 1));
291 	return (0);
292 }
293 
294 /*
295  * getzonestr(sa, zonestr)
296  *
297  * parse zone string from input sockaddr_in6
298  *
299  * Note:  This function calls if_indextoname, a very poor interface,
300  *        defined in RFC2553, for converting an interface index to an
301  *        interface name.  Callers of this function must be sure that
302  *        zonestr is atleast LIFNAMSIZ in length, since this is the longest
303  *        possible value if_indextoname will return.
304  *
305  * Return values:
306  * 0 an error with calling this function occured
307  * >0 zonestr is filled with a valid zoneid string and the return value is the
308  *    length of that string.
309  */
310 static size_t
311 getzonestr(const struct sockaddr_in6 *sa, char *zonestr, size_t zonelen)
312 {
313 	uint32_t ifindex;
314 	char *retstr;
315 
316 	if (zonestr == NULL) {
317 		return (0);
318 	}
319 
320 	/*
321 	 * Since this implementation only supports link scope addresses,
322 	 * there is a one-to-one mapping between interface index and
323 	 * sin6_scope_id.
324 	 */
325 	ifindex = sa->sin6_scope_id;
326 
327 	if ((retstr = if_indextoname(ifindex, zonestr)) != NULL) {
328 		return (strlen(retstr));
329 	} else {
330 		int n;
331 
332 		/*
333 		 * Failed to convert ifindex into an interface name,
334 		 * simply return the literal value of ifindex as
335 		 * a string.
336 		 */
337 		if ((n = snprintf(zonestr, zonelen, "%u",
338 		    ifindex)) < 0) {
339 			return (0);
340 		} else {
341 			if (n >= zonelen) {
342 				return (0);
343 			}
344 			return (n);
345 		}
346 	}
347 }
348 
349 
350 /*
351  * This is a wrapper function for inet_ntop(). In case the af is AF_INET6
352  * and the address pointed by src is a IPv4-mapped IPv6 address, it
353  * returns printable IPv4 address, not IPv4-mapped IPv6 address. In other cases
354  * it behaves just like inet_ntop().
355  */
356 static const char *
357 _inet_ntop_native(int af, const void *src, char *dst, size_t size)
358 {
359 	struct in_addr src4;
360 	const char *result;
361 
362 	if (af == AF_INET6) {
363 		if (IN6_IS_ADDR_V4MAPPED((struct in6_addr *)src)) {
364 			IN6_V4MAPPED_TO_INADDR((struct in6_addr *)src, &src4);
365 			result = inet_ntop(AF_INET, &src4, dst, size);
366 		} else {
367 			result = inet_ntop(AF_INET6, src, dst, size);
368 		}
369 	} else {
370 		result = inet_ntop(af, src, dst, size);
371 	}
372 
373 	return (result);
374 }
375