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