Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / tcp_wrappers / socket.c
1  /*
2   * This module determines the type of socket (datagram, stream), the client
3   * socket address and port, the server socket address and port. In addition,
4   * it provides methods to map a transport address to a printable host name
5   * or address. Socket address information results are in static memory.
6   * 
7   * The result from the hostname lookup method is STRING_PARANOID when a host
8   * pretends to have someone elses name, or when a host name is available but
9   * could not be verified.
10   * 
11   * When lookup or conversion fails the result is set to STRING_UNKNOWN.
12   * 
13   * Diagnostics are reported through syslog(3).
14   * 
15   * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
16   *
17   * $FreeBSD: src/contrib/tcp_wrappers/socket.c,v 1.2.2.3 2001/07/04 20:18:11 kris Exp $
18   */
19
20 #ifndef lint
21 static char sccsid[] = "@(#) socket.c 1.15 97/03/21 19:27:24";
22 #endif
23
24 /* System libraries. */
25
26 #include <sys/types.h>
27 #include <sys/param.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <netdb.h>
31 #include <stdio.h>
32 #include <syslog.h>
33 #include <string.h>
34
35 #ifdef INET6
36 #ifndef NI_WITHSCOPEID
37 #define NI_WITHSCOPEID  0
38 #endif
39 #else
40 extern char *inet_ntoa();
41 #endif
42
43 /* Local stuff. */
44
45 #include "tcpd.h"
46
47 /* Forward declarations. */
48
49 static void sock_sink();
50
51 #ifdef APPEND_DOT
52
53  /*
54   * Speed up DNS lookups by terminating the host name with a dot. Should be
55   * done with care. The speedup can give problems with lookups from sources
56   * that lack DNS-style trailing dot magic, such as local files or NIS maps.
57   */
58
59 static struct hostent *gethostbyname_dot(name)
60 char   *name;
61 {
62     char    dot_name[MAXHOSTNAMELEN + 1];
63
64     /*
65      * Don't append dots to unqualified names. Such names are likely to come
66      * from local hosts files or from NIS.
67      */
68
69     if (strchr(name, '.') == 0 || strlen(name) >= MAXHOSTNAMELEN - 1) {
70         return (gethostbyname(name));
71     } else {
72         sprintf(dot_name, "%s.", name);
73         return (gethostbyname(dot_name));
74     }
75 }
76
77 #define gethostbyname gethostbyname_dot
78 #endif
79
80 /* sock_host - look up endpoint addresses and install conversion methods */
81
82 void    sock_host(request)
83 struct request_info *request;
84 {
85 #ifdef INET6
86     static struct sockaddr_storage client;
87     static struct sockaddr_storage server;
88 #else
89     static struct sockaddr_in client;
90     static struct sockaddr_in server;
91 #endif
92     int     len;
93     char    buf[BUFSIZ];
94     int     fd = request->fd;
95
96     sock_methods(request);
97
98     /*
99      * Look up the client host address. Hal R. Brand <BRAND@addvax.llnl.gov>
100      * suggested how to get the client host info in case of UDP connections:
101      * peek at the first message without actually looking at its contents. We
102      * really should verify that client.sin_family gets the value AF_INET,
103      * but this program has already caused too much grief on systems with
104      * broken library code.
105      */
106
107     len = sizeof(client);
108     if (getpeername(fd, (struct sockaddr *) & client, &len) < 0) {
109         request->sink = sock_sink;
110         len = sizeof(client);
111         if (recvfrom(fd, buf, sizeof(buf), MSG_PEEK,
112                      (struct sockaddr *) & client, &len) < 0) {
113             tcpd_warn("can't get client address: %m");
114             return;                             /* give up */
115         }
116 #ifdef really_paranoid
117         memset(buf, 0 sizeof(buf));
118 #endif
119     }
120 #ifdef INET6
121     request->client->sin = (struct sockaddr *)&client;
122 #else
123     request->client->sin = &client;
124 #endif
125
126     /*
127      * Determine the server binding. This is used for client username
128      * lookups, and for access control rules that trigger on the server
129      * address or name.
130      */
131
132     len = sizeof(server);
133     if (getsockname(fd, (struct sockaddr *) & server, &len) < 0) {
134         tcpd_warn("getsockname: %m");
135         return;
136     }
137 #ifdef INET6
138     request->server->sin = (struct sockaddr *)&server;
139 #else
140     request->server->sin = &server;
141 #endif
142 }
143
144 /* sock_hostaddr - map endpoint address to printable form */
145
146 void    sock_hostaddr(host)
147 struct host_info *host;
148 {
149 #ifdef INET6
150     struct sockaddr *sin = host->sin;
151     int salen;
152
153     if (!sin)
154         return;
155 #ifdef SIN6_LEN
156     salen = sin->sa_len;
157 #else
158     salen = (sin->sa_family == AF_INET) ? sizeof(struct sockaddr_in)
159                                         : sizeof(struct sockaddr_in6);
160 #endif
161     getnameinfo(sin, salen, host->addr, sizeof(host->addr),
162                 NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID);
163 #else
164     struct sockaddr_in *sin = host->sin;
165
166     if (sin != 0)
167         STRN_CPY(host->addr, inet_ntoa(sin->sin_addr), sizeof(host->addr));
168 #endif
169 }
170
171 /* sock_hostname - map endpoint address to host name */
172
173 void    sock_hostname(host)
174 struct host_info *host;
175 {
176 #ifdef INET6
177     struct sockaddr *sin = host->sin;
178     struct sockaddr_in sin4;
179     struct addrinfo hints, *res, *res0 = NULL;
180     int salen, alen, err = 1;
181     char *ap = NULL, *rap, hname[NI_MAXHOST];
182
183     if (sin != NULL) {
184         if (sin->sa_family == AF_INET6) {
185             struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sin;
186
187             if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
188                 memset(&sin4, 0, sizeof(sin4));
189 #ifdef SIN6_LEN
190                 sin4.sin_len = sizeof(sin4);
191 #endif
192                 sin4.sin_family = AF_INET;
193                 sin4.sin_port = sin6->sin6_port;
194                 sin4.sin_addr.s_addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
195                 sin = (struct sockaddr *)&sin4;
196             }
197         }
198         switch (sin->sa_family) {
199         case AF_INET:
200             ap = (char *)&((struct sockaddr_in *)sin)->sin_addr;
201             alen = sizeof(struct in_addr);
202             salen = sizeof(struct sockaddr_in);
203             break;
204         case AF_INET6:
205             ap = (char *)&((struct sockaddr_in6 *)sin)->sin6_addr;
206             alen = sizeof(struct in6_addr);
207             salen = sizeof(struct sockaddr_in6);
208             break;
209         default:
210             break;
211         }
212         if (ap)
213             err = getnameinfo(sin, salen, hname, sizeof(hname),
214                               NULL, 0, NI_WITHSCOPEID | NI_NAMEREQD);
215     }
216     if (!err) {
217
218         STRN_CPY(host->name, hname, sizeof(host->name));
219
220         /* reject numeric addresses */
221         memset(&hints, 0, sizeof(hints));
222         hints.ai_family = sin->sa_family;
223         hints.ai_socktype = SOCK_STREAM;
224         hints.ai_flags = AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST;
225         if ((err = getaddrinfo(host->name, NULL, &hints, &res0)) == 0) {
226             freeaddrinfo(res0);
227             tcpd_warn("host name/name mismatch: "
228                       "reverse lookup results in non-FQDN %s",
229                       host->name);
230             strcpy(host->name, paranoid);       /* name is bad, clobber it */
231         }
232         err = !err;
233     }
234     if (!err) {
235         /* we are now sure that this is non-numeric */
236
237         /*
238          * Verify that the address is a member of the address list returned
239          * by gethostbyname(hostname).
240          * 
241          * Verify also that gethostbyaddr() and gethostbyname() return the same
242          * hostname, or rshd and rlogind may still end up being spoofed.
243          * 
244          * On some sites, gethostbyname("localhost") returns "localhost.domain".
245          * This is a DNS artefact. We treat it as a special case. When we
246          * can't believe the address list from gethostbyname("localhost")
247          * we're in big trouble anyway.
248          */
249
250         memset(&hints, 0, sizeof(hints));
251         hints.ai_family = sin->sa_family;
252         hints.ai_socktype = SOCK_STREAM;
253         hints.ai_flags = AI_PASSIVE | AI_CANONNAME;
254         if (getaddrinfo(host->name, NULL, &hints, &res0) != 0) {
255
256             /*
257              * Unable to verify that the host name matches the address. This
258              * may be a transient problem or a botched name server setup.
259              */
260
261             tcpd_warn("can't verify hostname: getaddrinfo(%s, %s) failed",
262                       host->name,
263                       (sin->sa_family == AF_INET) ? "AF_INET" : "AF_INET6");
264
265         } else if ((res0->ai_canonname == NULL
266                     || STR_NE(host->name, res0->ai_canonname))
267                    && STR_NE(host->name, "localhost")) {
268
269             /*
270              * The gethostbyaddr() and gethostbyname() calls did not return
271              * the same hostname. This could be a nameserver configuration
272              * problem. It could also be that someone is trying to spoof us.
273              */
274
275             tcpd_warn("host name/name mismatch: %s != %.*s",
276                       host->name, STRING_LENGTH,
277                       (res0->ai_canonname == NULL) ? "" : res0->ai_canonname);
278
279         } else {
280
281             /*
282              * The address should be a member of the address list returned by
283              * gethostbyname(). We should first verify that the h_addrtype
284              * field is AF_INET, but this program has already caused too much
285              * grief on systems with broken library code.
286              */
287
288             for (res = res0; res; res = res->ai_next) {
289                 if (res->ai_family != sin->sa_family)
290                     continue;
291                 switch (res->ai_family) {
292                 case AF_INET:
293                     rap = (char *)&((struct sockaddr_in *)res->ai_addr)->sin_addr;
294                     break;
295                 case AF_INET6:
296                     /* need to check scope_id */
297                     if (((struct sockaddr_in6 *)sin)->sin6_scope_id !=
298                         ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id) {
299                         continue;
300                     }
301                     rap = (char *)&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
302                     break;
303                 default:
304                     continue;
305                 }
306                 if (memcmp(rap, ap, alen) == 0) {
307                     freeaddrinfo(res0);
308                     return;                     /* name is good, keep it */
309                 }
310             }
311
312             /*
313              * The host name does not map to the initial address. Perhaps
314              * someone has messed up. Perhaps someone compromised a name
315              * server.
316              */
317
318             getnameinfo(sin, salen, hname, sizeof(hname),
319                         NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID);
320             tcpd_warn("host name/address mismatch: %s != %.*s",
321                       hname, STRING_LENGTH,
322                       (res0->ai_canonname == NULL) ? "" : res0->ai_canonname);
323         }
324         strcpy(host->name, paranoid);           /* name is bad, clobber it */
325         if (res0)
326             freeaddrinfo(res0);
327     }
328 #else /* INET6 */
329     struct sockaddr_in *sin = host->sin;
330     struct hostent *hp;
331     int     i;
332
333     /*
334      * On some systems, for example Solaris 2.3, gethostbyaddr(0.0.0.0) does
335      * not fail. Instead it returns "INADDR_ANY". Unfortunately, this does
336      * not work the other way around: gethostbyname("INADDR_ANY") fails. We
337      * have to special-case 0.0.0.0, in order to avoid false alerts from the
338      * host name/address checking code below.
339      */
340     if (sin != 0 && sin->sin_addr.s_addr != 0
341         && (hp = gethostbyaddr((char *) &(sin->sin_addr),
342                                sizeof(sin->sin_addr), AF_INET)) != 0) {
343
344         STRN_CPY(host->name, hp->h_name, sizeof(host->name));
345
346         /*
347          * Verify that the address is a member of the address list returned
348          * by gethostbyname(hostname).
349          * 
350          * Verify also that gethostbyaddr() and gethostbyname() return the same
351          * hostname, or rshd and rlogind may still end up being spoofed.
352          * 
353          * On some sites, gethostbyname("localhost") returns "localhost.domain".
354          * This is a DNS artefact. We treat it as a special case. When we
355          * can't believe the address list from gethostbyname("localhost")
356          * we're in big trouble anyway.
357          */
358
359         if ((hp = gethostbyname(host->name)) == 0) {
360
361             /*
362              * Unable to verify that the host name matches the address. This
363              * may be a transient problem or a botched name server setup.
364              */
365
366             tcpd_warn("can't verify hostname: gethostbyname(%s) failed",
367                       host->name);
368
369         } else if (STR_NE(host->name, hp->h_name)
370                    && STR_NE(host->name, "localhost")) {
371
372             /*
373              * The gethostbyaddr() and gethostbyname() calls did not return
374              * the same hostname. This could be a nameserver configuration
375              * problem. It could also be that someone is trying to spoof us.
376              */
377
378             tcpd_warn("host name/name mismatch: %s != %.*s",
379                       host->name, STRING_LENGTH, hp->h_name);
380
381         } else {
382
383             /*
384              * The address should be a member of the address list returned by
385              * gethostbyname(). We should first verify that the h_addrtype
386              * field is AF_INET, but this program has already caused too much
387              * grief on systems with broken library code.
388              */
389
390             for (i = 0; hp->h_addr_list[i]; i++) {
391                 if (memcmp(hp->h_addr_list[i],
392                            (char *) &sin->sin_addr,
393                            sizeof(sin->sin_addr)) == 0)
394                     return;                     /* name is good, keep it */
395             }
396
397             /*
398              * The host name does not map to the initial address. Perhaps
399              * someone has messed up. Perhaps someone compromised a name
400              * server.
401              */
402
403             tcpd_warn("host name/address mismatch: %s != %.*s",
404                       inet_ntoa(sin->sin_addr), STRING_LENGTH, hp->h_name);
405         }
406         strcpy(host->name, paranoid);           /* name is bad, clobber it */
407     }
408 #endif /* INET6 */
409 }
410
411 /* sock_sink - absorb unreceived IP datagram */
412
413 static void sock_sink(fd)
414 int     fd;
415 {
416     char    buf[BUFSIZ];
417 #ifdef INET6
418     struct sockaddr_storage sin;
419 #else
420     struct sockaddr_in sin;
421 #endif
422     int     size = sizeof(sin);
423
424     /*
425      * Eat up the not-yet received datagram. Some systems insist on a
426      * non-zero source address argument in the recvfrom() call below.
427      */
428
429     (void) recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) & sin, &size);
430 }