144743Smarkm /*
244743Smarkm  * Workarounds for known system software bugs. This module provides wrappers
344743Smarkm  * around library functions and system calls that are known to have problems
444743Smarkm  * on some systems. Most of these workarounds won't do any harm on regular
544743Smarkm  * systems.
644743Smarkm  *
744743Smarkm  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
856977Sshin  *
956977Sshin  * $FreeBSD$
1044743Smarkm  */
1144743Smarkm
1244743Smarkm#ifndef lint
1344743Smarkmchar    sccsid[] = "@(#) workarounds.c 1.6 96/03/19 16:22:25";
1444743Smarkm#endif
1544743Smarkm
1644743Smarkm#include <sys/types.h>
1744743Smarkm#include <sys/param.h>
1844743Smarkm#include <sys/socket.h>
1944743Smarkm#include <netinet/in.h>
2044743Smarkm#include <arpa/inet.h>
2144743Smarkm#include <netdb.h>
2244743Smarkm#include <errno.h>
2344743Smarkm#include <stdio.h>
2444743Smarkm#include <syslog.h>
2544743Smarkm#include <string.h>
2644743Smarkm
2744743Smarkmextern int errno;
2844743Smarkm
2944743Smarkm#include "tcpd.h"
3044743Smarkm
3144743Smarkm /*
3244743Smarkm  * Some AIX versions advertise a too small MAXHOSTNAMELEN value (32).
3344743Smarkm  * Result: long hostnames would be truncated, and connections would be
3444743Smarkm  * dropped because of host name verification failures. Adrian van Bloois
3544743Smarkm  * (A.vanBloois@info.nic.surfnet.nl) figured out what was the problem.
3644743Smarkm  */
3744743Smarkm
3844743Smarkm#if (MAXHOSTNAMELEN < 64)
3944743Smarkm#undef MAXHOSTNAMELEN
4044743Smarkm#endif
4144743Smarkm
4244743Smarkm/* In case not defined in <sys/param.h>. */
4344743Smarkm
4444743Smarkm#ifndef MAXHOSTNAMELEN
4544743Smarkm#define MAXHOSTNAMELEN  256             /* storage for host name */
4644743Smarkm#endif
4744743Smarkm
4844743Smarkm /*
4944743Smarkm  * Some DG/UX inet_addr() versions return a struct/union instead of a long.
5044743Smarkm  * You have this problem when the compiler complains about illegal lvalues
5144743Smarkm  * or something like that. The following code fixes this mutant behaviour.
5244743Smarkm  * It should not be enabled on "normal" systems.
5344743Smarkm  *
5444743Smarkm  * Bug reported by ben@piglet.cr.usgs.gov (Rev. Ben A. Mesander).
5544743Smarkm  */
5644743Smarkm
5744743Smarkm#ifdef INET_ADDR_BUG
5844743Smarkm
5944743Smarkm#undef inet_addr
6044743Smarkm
6144743Smarkmlong    fix_inet_addr(string)
6244743Smarkmchar   *string;
6344743Smarkm{
6444743Smarkm    return (inet_addr(string).s_addr);
6544743Smarkm}
6644743Smarkm
6744743Smarkm#endif /* INET_ADDR_BUG */
6844743Smarkm
6944743Smarkm /*
7044743Smarkm  * With some System-V versions, the fgets() library function does not
7144743Smarkm  * account for partial reads from e.g. sockets. The result is that fgets()
7244743Smarkm  * gives up too soon, causing username lookups to fail. Problem first
7344743Smarkm  * reported for IRIX 4.0.5, by Steve Kotsopoulos <steve@ecf.toronto.edu>.
7444743Smarkm  * The following code works around the problem. It does no harm on "normal"
7544743Smarkm  * systems.
7644743Smarkm  */
7744743Smarkm
7844743Smarkm#ifdef BROKEN_FGETS
7944743Smarkm
8044743Smarkm#undef fgets
8144743Smarkm
8244743Smarkmchar   *fix_fgets(buf, len, fp)
8344743Smarkmchar   *buf;
8444743Smarkmint     len;
8544743SmarkmFILE   *fp;
8644743Smarkm{
8744743Smarkm    char   *cp = buf;
8844743Smarkm    int     c;
8944743Smarkm
9044743Smarkm    /*
9144743Smarkm     * Copy until the buffer fills up, until EOF, or until a newline is
9244743Smarkm     * found.
9344743Smarkm     */
9444743Smarkm    while (len > 1 && (c = getc(fp)) != EOF) {
9544743Smarkm	len--;
9644743Smarkm	*cp++ = c;
9744743Smarkm	if (c == '\n')
9844743Smarkm	    break;
9944743Smarkm    }
10044743Smarkm
10144743Smarkm    /*
10244743Smarkm     * Return 0 if nothing was read. This is correct even when a silly buffer
10344743Smarkm     * length was specified.
10444743Smarkm     */
10544743Smarkm    if (cp > buf) {
10644743Smarkm	*cp = 0;
10744743Smarkm	return (buf);
10844743Smarkm    } else {
10944743Smarkm	return (0);
11044743Smarkm    }
11144743Smarkm}
11244743Smarkm
11344743Smarkm#endif /* BROKEN_FGETS */
11444743Smarkm
11544743Smarkm /*
11644743Smarkm  * With early SunOS 5 versions, recvfrom() does not completely fill in the
11744743Smarkm  * source address structure when doing a non-destructive read. The following
11844743Smarkm  * code works around the problem. It does no harm on "normal" systems.
11944743Smarkm  */
12044743Smarkm
12144743Smarkm#ifdef RECVFROM_BUG
12244743Smarkm
12344743Smarkm#undef recvfrom
12444743Smarkm
12544743Smarkmint     fix_recvfrom(sock, buf, buflen, flags, from, fromlen)
12644743Smarkmint     sock;
12744743Smarkmchar   *buf;
12844743Smarkmint     buflen;
12944743Smarkmint     flags;
13044743Smarkmstruct sockaddr *from;
13144743Smarkmint    *fromlen;
13244743Smarkm{
13344743Smarkm    int     ret;
13444743Smarkm
13544743Smarkm    /* Assume that both ends of a socket belong to the same address family. */
13644743Smarkm
13744743Smarkm    if ((ret = recvfrom(sock, buf, buflen, flags, from, fromlen)) >= 0) {
13844743Smarkm	if (from->sa_family == 0) {
13944743Smarkm	    struct sockaddr my_addr;
14044743Smarkm	    int     my_addr_len = sizeof(my_addr);
14144743Smarkm
14244743Smarkm	    if (getsockname(0, &my_addr, &my_addr_len)) {
14344743Smarkm		tcpd_warn("getsockname: %m");
14444743Smarkm	    } else {
14544743Smarkm		from->sa_family = my_addr.sa_family;
14644743Smarkm	    }
14744743Smarkm	}
14844743Smarkm    }
14944743Smarkm    return (ret);
15044743Smarkm}
15144743Smarkm
15244743Smarkm#endif /* RECVFROM_BUG */
15344743Smarkm
15444743Smarkm /*
15544743Smarkm  * The Apollo SR10.3 and some SYSV4 getpeername(2) versions do not return an
15644743Smarkm  * error in case of a datagram-oriented socket. Instead, they claim that all
15744743Smarkm  * UDP requests come from address 0.0.0.0. The following code works around
15844743Smarkm  * the problem. It does no harm on "normal" systems.
15944743Smarkm  */
16044743Smarkm
16144743Smarkm#ifdef GETPEERNAME_BUG
16244743Smarkm
16344743Smarkm#undef getpeername
16444743Smarkm
16544743Smarkmint     fix_getpeername(sock, sa, len)
16644743Smarkmint     sock;
16744743Smarkmstruct sockaddr *sa;
16844743Smarkmint    *len;
16944743Smarkm{
17044743Smarkm    int     ret;
17156977Sshin#ifdef INET6
17256977Sshin    struct sockaddr *sin = sa;
17356977Sshin#else
17444743Smarkm    struct sockaddr_in *sin = (struct sockaddr_in *) sa;
17556977Sshin#endif
17644743Smarkm
17744743Smarkm    if ((ret = getpeername(sock, sa, len)) >= 0
17856977Sshin#ifdef INET6
17956977Sshin	&& ((sin->su_si.si_family == AF_INET6
18056977Sshin	     && IN6_IS_ADDR_UNSPECIFIED(&sin->su_sin6.sin6_addr))
18156977Sshin	    || (sin->su_si.si_family == AF_INET
18256977Sshin		&& sin->su_sin.sin_addr.s_addr == 0))) {
18356977Sshin#else
18444743Smarkm	&& sa->sa_family == AF_INET
18544743Smarkm	&& sin->sin_addr.s_addr == 0) {
18656977Sshin#endif
18744743Smarkm	errno = ENOTCONN;
18844743Smarkm	return (-1);
18944743Smarkm    } else {
19044743Smarkm	return (ret);
19144743Smarkm    }
19244743Smarkm}
19344743Smarkm
19444743Smarkm#endif /* GETPEERNAME_BUG */
19544743Smarkm
19644743Smarkm /*
19744743Smarkm  * According to Karl Vogel (vogelke@c-17igp.wpafb.af.mil) some Pyramid
19844743Smarkm  * versions have no yp_default_domain() function. We use getdomainname()
19944743Smarkm  * instead.
20044743Smarkm  */
20144743Smarkm
20244743Smarkm#ifdef USE_GETDOMAIN
20344743Smarkm
20444743Smarkmint     yp_get_default_domain(ptr)
20544743Smarkmchar  **ptr;
20644743Smarkm{
20744743Smarkm    static char mydomain[MAXHOSTNAMELEN];
20844743Smarkm
20944743Smarkm    *ptr = mydomain;
21044743Smarkm    return (getdomainname(mydomain, MAXHOSTNAMELEN));
21144743Smarkm}
21244743Smarkm
21344743Smarkm#endif /* USE_GETDOMAIN */
21444743Smarkm
21544743Smarkm#ifndef INADDR_NONE
21644743Smarkm#define INADDR_NONE 0xffffffff
21744743Smarkm#endif
21844743Smarkm
21944743Smarkm /*
22044743Smarkm  * Solaris 2.4 gethostbyname() has problems with multihomed hosts. When
22144743Smarkm  * doing DNS through NIS, only one host address ends up in the address list.
22244743Smarkm  * All other addresses end up in the hostname alias list, interspersed with
22344743Smarkm  * copies of the official host name. This would wreak havoc with tcpd's
22444743Smarkm  * hostname double checks. Below is a workaround that should do no harm when
22544743Smarkm  * accidentally left in. A side effect of the workaround is that address
22644743Smarkm  * list members are no longer properly aligned for structure access.
22744743Smarkm  */
22844743Smarkm
22944743Smarkm#ifdef SOLARIS_24_GETHOSTBYNAME_BUG
23044743Smarkm
23144743Smarkm#undef gethostbyname
23244743Smarkm
23344743Smarkmstruct hostent *fix_gethostbyname(name)
23444743Smarkmchar   *name;
23544743Smarkm{
23644743Smarkm    struct hostent *hp;
23744743Smarkm    struct in_addr addr;
23844743Smarkm    char  **o_addr_list;
23944743Smarkm    char  **o_aliases;
24044743Smarkm    char  **n_addr_list;
24144743Smarkm    int     broken_gethostbyname = 0;
24244743Smarkm
24344743Smarkm    if ((hp = gethostbyname(name)) && !hp->h_addr_list[1] && hp->h_aliases[1]) {
24444743Smarkm	for (o_aliases = n_addr_list = hp->h_aliases; *o_aliases; o_aliases++) {
24544743Smarkm	    if ((addr.s_addr = inet_addr(*o_aliases)) != INADDR_NONE) {
24644743Smarkm		memcpy(*n_addr_list++, (char *) &addr, hp->h_length);
24744743Smarkm		broken_gethostbyname = 1;
24844743Smarkm	    }
24944743Smarkm	}
25044743Smarkm	if (broken_gethostbyname) {
25144743Smarkm	    o_addr_list = hp->h_addr_list;
25244743Smarkm	    memcpy(*n_addr_list++, *o_addr_list, hp->h_length);
25344743Smarkm	    *n_addr_list = 0;
25444743Smarkm	    hp->h_addr_list = hp->h_aliases;
25544743Smarkm	    hp->h_aliases = o_addr_list + 1;
25644743Smarkm	}
25744743Smarkm    }
25844743Smarkm    return (hp);
25944743Smarkm}
26044743Smarkm
26144743Smarkm#endif /* SOLARIS_24_GETHOSTBYNAME_BUG */
26244743Smarkm
26344743Smarkm /*
26444743Smarkm  * Horror! Some FreeBSD 2.0 libc routines call strtok(). Since tcpd depends
26544743Smarkm  * heavily on strtok(), strange things may happen. Workaround: use our
26644743Smarkm  * private strtok(). This has been fixed in the meantime.
26744743Smarkm  */
26844743Smarkm
26944743Smarkm#ifdef USE_STRSEP
27044743Smarkm
27144743Smarkmchar   *fix_strtok(buf, sep)
27244743Smarkmchar   *buf;
27344743Smarkmchar   *sep;
27444743Smarkm{
27544743Smarkm    static char *state;
27644743Smarkm    char   *result;
27744743Smarkm
27844743Smarkm    if (buf)
27944743Smarkm	state = buf;
28044743Smarkm    while ((result = strsep(&state, sep)) && result[0] == 0)
28144743Smarkm	 /* void */ ;
28244743Smarkm    return (result);
28344743Smarkm}
28444743Smarkm
28544743Smarkm#endif /* USE_STRSEP */
28644743Smarkm
28744743Smarkm /*
28844743Smarkm  * IRIX 5.3 (and possibly earlier versions, too) library routines call the
28944743Smarkm  * non-reentrant strtok() library routine, causing hosts to slip through
29044743Smarkm  * allow/deny filters. Workaround: don't rely on the vendor and use our own
29144743Smarkm  * strtok() function. FreeBSD 2.0 has a similar problem (fixed in 2.0.5).
29244743Smarkm  */
29344743Smarkm
29444743Smarkm#ifdef LIBC_CALLS_STRTOK
29544743Smarkm
29644743Smarkmchar   *my_strtok(buf, sep)
29744743Smarkmchar   *buf;
29844743Smarkmchar   *sep;
29944743Smarkm{
30044743Smarkm    static char *state;
30144743Smarkm    char   *result;
30244743Smarkm
30344743Smarkm    if (buf)
30444743Smarkm	state = buf;
30544743Smarkm
30644743Smarkm    /*
30744743Smarkm     * Skip over separator characters and detect end of string.
30844743Smarkm     */
30944743Smarkm    if (*(state += strspn(state, sep)) == 0)
31044743Smarkm	return (0);
31144743Smarkm
31244743Smarkm    /*
31344743Smarkm     * Skip over non-separator characters and terminate result.
31444743Smarkm     */
31544743Smarkm    result = state;
31644743Smarkm    if (*(state += strcspn(state, sep)) != 0)
31744743Smarkm	*state++ = 0;
31844743Smarkm    return (result);
31944743Smarkm}
32044743Smarkm
32144743Smarkm#endif /* LIBC_CALLS_STRTOK */
322