From 0d0c0c016d2a6678b776fbfba4875b7e221dde2d Mon Sep 17 00:00:00 2001 From: Ken Raeburn Date: Tue, 12 Mar 2002 03:54:58 +0000 Subject: [PATCH] Restructure a little to permit wrapping a system implementation of getaddrinfo and friends in a function that does some fixups on the returned data. Use this for Linux and AIX. Also separate the replacement versions as functions with different names so that Windows can (eventually) use them from a wrapper function if a system version cannot be located. git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@14262 dc483132-0cff-0310-8789-dd5450dbe970 --- src/include/ChangeLog | 25 +++ src/include/fake-addrinfo.c | 299 +++++++++++++++++++++++++++--------- src/include/fake-addrinfo.h | 29 +++- 3 files changed, 273 insertions(+), 80 deletions(-) diff --git a/src/include/ChangeLog b/src/include/ChangeLog index 7cfea4524..7e3412655 100644 --- a/src/include/ChangeLog +++ b/src/include/ChangeLog @@ -1,3 +1,28 @@ +2002-03-11 Ken Raeburn + + * fake-addrinfo.c (fixup_addrinfo): Deleted. + (fake_getaddrinfo, fake_getnameinfo, fake_freeaddrinfo): Renamed + from non-"fake_" versions, and made static. Compile if + NEED_FAKE_GETADDRINFO is defined. + (fake_getnameinfo): Truncate results if provided buffers are too + small. + (getaddrinfo, getnameinfo, freeaddrinfo) [HAVE_FAKE_GETADDRINFO]: + New functions, simple wrappers around the "fake_" versions. + (getaddrinfo, freeaddrinfo) [WRAP_GETADDRINFO]: New functions + which call the system versions via function pointers and then fix + up some known problems in the returned data. + (getnameinfo) [WRAP_GETNAMEINFO]: Likewise. + (gaiptr, faiptr, gniptr) [WRAP_GETADDRINFO || WRAP_GETNAMEINFO]: + New static variables, initialized with addresses of system + versions of getaddrinfo, etc. + * fake-addrinfo.h (fixup_addrinfo): Declaration deleted. + (WRAP_GETADDRINFO): New macro, defined on Linux and AIX. + (getaddrinfo, getnameinfo, freeaddrinfo): Define as macros, and + declare functions, if WRAP_GETADDRINFO is defined or + HAVE_GETADDRINFO is not defined; drop BROKEN_GETADDRINFO check. + (gai_strerror, addrinfo, EAI_*): Define macros and declare + functions only if HAVE_GETADDRINFO isn't defined. + 2002-03-06 Ken Raeburn * krb5.hin (krb5_const): Restore macro definition, but include a diff --git a/src/include/fake-addrinfo.c b/src/include/fake-addrinfo.c index a78e9daf4..a2981166d 100644 --- a/src/include/fake-addrinfo.c +++ b/src/include/fake-addrinfo.c @@ -41,10 +41,32 @@ /* To do, maybe: - IPv6 support for systems with working inet6 socket code but broken - getaddrinfo implementations? (RH Linux 6.1 libc getaddrinfo - ignores AI_NUMERICHOST. Solaris 8 doesn't appear to support - IPv6.) Could use gethostbyname2 if available. */ + + For AIX 4.3.3, using the RFC 2133 definition: Implement + AI_NUMERICHOST. It's not defined in the header file. + + For certain (old?) versions of GNU libc, AI_NUMERICHOST is + defined but not implemented. + + + Windows support. + + Apparently XP and .Net provide getaddrinfo and friends, but + earlier versions do not. Since we want one binary to work on + multiple platforms, the best option appears to be to use the OS + version if it's available, and the fake one here otherwise. This + means both the wrapper and the fake versions need to be compiled, + and they need to use the OS version of structures and macros. + + Try defining NEED_FAKE_GETADDRINFO and WRAP_GETADDRINFO but not + HAVE_FAKE_GETADDRINFO, and put in the right magic to look up the + function addresses at run time. + + + Use gethostbyname2, inet_aton and other IPv6 or thread-safe + functions if available. But, see + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=135182 for one + gethostbyname2 problem on Linux. + + + Upgrade host requirements to include working implementations of + these functions, and throw all this away. :-) */ #include "fake-addrinfo.h" @@ -55,54 +77,11 @@ # undef _XOPEN_SOURCE_EXTENDED #endif -void fixup_addrinfo (struct addrinfo *ai) -{ - struct addrinfo *ai2; - - if (ai == 0) - return; - - /* Linux libc version 6 (libc-2.2.4.so on Debian) is broken. - - RFC 2553 says that when AI_CANONNAME is set, the ai_canonname - flag of the first returned structure has the canonical name of - the host. Instead, GNU libc sets ai_canonname in all the - returned structures, sometimes it's the canonical name and - sometimes it's the numeric form of an address. So if we - actually want the canonical name, we may have to look through - the list and discard numeric addresses. - - Since it's dependent on the target hostname, it's hard to check - for at configure time. */ - if (ai->ai_canonname && strchr(ai->ai_canonname, ':')) { - for (ai2 = ai->ai_next; ai2; ai2 = ai2->ai_next) { - if (ai2->ai_canonname == 0) - continue; - if (!strchr(ai2->ai_canonname, ':')) { - char *p = ai->ai_canonname; - ai->ai_canonname = ai2->ai_canonname; - ai2->ai_canonname = p; - break; - } - } - if (ai2 == 0) - /* Ran off end, with no non-numeric name. What to do? */ - ; - } - - for (; ai; ai = ai->ai_next) { - /* AIX 4.3.3 libc is broken. It doesn't set the family or len - fields of the sockaddr structures. */ - if (ai->ai_addr->sa_family == 0) - ai->ai_addr->sa_family = ai->ai_family; -#ifdef HAVE_SA_LEN - if (ai->ai_addr->sa_len == 0) - ai->ai_addr->sa_len = ai->ai_addrlen; +#ifdef HAVE_FAKE_GETADDRINFO +#define NEED_FAKE_GETADDRINFO #endif - } -} -#ifdef HAVE_FAKE_GETADDRINFO +#ifdef NEED_FAKE_GETADDRINFO static int translate_h_errno (int h); @@ -155,8 +134,9 @@ static int fai_add_hosts_by_name (const char *name, int af, return 0; } -int getaddrinfo (const char *name, const char *serv, - const struct addrinfo *hint, struct addrinfo **result) +static int +fake_getaddrinfo (const char *name, const char *serv, + const struct addrinfo *hint, struct addrinfo **result) { struct addrinfo *res = 0; int ret; @@ -246,10 +226,11 @@ int getaddrinfo (const char *name, const char *serv, return 0; } -int getnameinfo (const struct sockaddr *sa, socklen_t len, - char *host, size_t hostlen, - char *service, size_t servicelen, - int flags) +static int +fake_getnameinfo (const struct sockaddr *sa, socklen_t len, + char *host, size_t hostlen, + char *service, size_t servicelen, + int flags) { struct hostent *hp; const struct sockaddr_in *sinp; @@ -265,10 +246,7 @@ int getnameinfo (const struct sockaddr *sa, socklen_t len, char *p; numeric_host: p = inet_ntoa (sinp->sin_addr); - if (strlen (p) < hostlen) - strcpy (host, p); - else - return EAI_FAIL; /* ?? */ + strncpy (host, p, hostlen); } else { hp = gethostbyaddr ((const char *) &sinp->sin_addr, sizeof (struct in_addr), @@ -278,11 +256,12 @@ int getnameinfo (const struct sockaddr *sa, socklen_t len, goto numeric_host; return translate_h_errno (h_errno); } - if (strlen (hp->h_name) < hostlen) - strcpy (host, hp->h_name); - else - return EAI_FAIL; /* ?? */ + /* According to the Open Group spec, getnameinfo can + silently truncate, but must still return a + null-terminated string. */ + strncpy (host, hp->h_name, hostlen); } + host[hostlen-1] = 0; } if (service) { @@ -294,26 +273,22 @@ int getnameinfo (const struct sockaddr *sa, socklen_t len, if (port < 0 || port > 65535) return EAI_FAIL; sprintf (numbuf, "%d", port); - if (strlen (numbuf) < servicelen) - strcpy (service, numbuf); - else - return EAI_FAIL; + strncpy (service, numbuf, servicelen); } else { sp = getservbyport (sinp->sin_port, (flags & NI_DGRAM) ? "udp" : "tcp"); if (sp == 0) goto numeric_service; - if (strlen (sp->s_name) < servicelen) - strcpy (service, sp->s_name); - else - return EAI_FAIL; + strncpy (service, sp->s_name, servicelen); } + service[servicelen-1] = 0; } return 0; } -void freeaddrinfo (struct addrinfo *ai) +static void +fake_freeaddrinfo (struct addrinfo *ai) { struct addrinfo *next; while (ai) { @@ -372,4 +347,182 @@ static int translate_h_errno (int h) } } +#ifdef HAVE_FAKE_GETADDRINFO +int getaddrinfo (const char *name, const char *serv, + const struct addrinfo *hint, struct addrinfo **result) +{ + return fake_getaddrinfo(name, serv, hint, result); +} + +void freeaddrinfo (struct addrinfo *ai) +{ + fake_freeaddrinfo(ai); +} + +int getnameinfo (const struct sockaddr *sa, socklen_t len, + char *host, size_t hostlen, + char *service, size_t servicelen, + int flags) +{ + return fake_getnameinfo(sa, len, host, hostlen, service, servicelen, + flags); +} +#endif /* HAVE_FAKE_GETADDRINFO */ +#endif /* NEED_FAKE_GETADDRINFO */ + + +#if defined (WRAP_GETADDRINFO) || defined (WRAP_GETNAMEINFO) +/* These variables will contain pointers to the system versions. They + have to be initialized at the end, because the way we initialize + them (for UNIX) is #undef and a reference to the C library symbol + name. */ +static int (*gaiptr) (const char *, const char *, const struct addrinfo *, + struct addrinfo **); +static void (*faiptr) (struct addrinfo *); +#ifdef WRAP_GETNAMEINFO +static int (*gniptr) (const struct sockaddr *, socklen_t, + char *, size_t, char *, size_t, int); +#endif + +#ifdef WRAP_GETADDRINFO + +int +getaddrinfo (const char *name, const char *serv, const struct addrinfo *hint, + struct addrinfo **result) +{ + int aierr; + + aierr = (*gaiptr) (name, serv, hint, result); + if (aierr || *result == 0) + return aierr; + +#ifdef __linux__ + /* Linux libc version 6 (libc-2.2.4.so on Debian) is broken. + + RFC 2553 says that when AI_CANONNAME is set, the ai_canonname + flag of the first returned structure has the canonical name of + the host. Instead, GNU libc sets ai_canonname in each returned + structure to the name that the corresponding address maps to, + if any, or a printable numeric form. + + RFC 2553 bis and the new Open Group spec say that field will be + the canonical name if it can be determined, otherwise, the + provided hostname or a copy of it. + + IMNSHO, "canonical name" means CNAME processing and not PTR + processing, but I can see arguing it. Using the numeric form + when that's not the form provided is just wrong. So, let's fix + it. + + The glibc 2.2.5 sources indicate that the canonical name is + *not* allocated separately, it's just some extra storage tacked + on the end of the addrinfo structure. So, let's try this + approach: If getaddrinfo sets ai_canonname, we'll replace the + *first* one with allocated storage, and free up that pointer in + freeaddrinfo if it's set; the other ai_canonname fields will be + left untouched. + + Ref: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=133668 . + + Since it's dependent on the target hostname, it's hard to check + for at configure time. Always do it on Linux for now. When + they get around to fixing it, add a compile-time or run-time + check for the glibc version in use. */ +#define COPY_FIRST_CANONNAME + if (name && (hint->ai_flags & AI_CANONNAME)) { + struct hostent *hp; + const char *name2 = 0; + int i; + + hp = gethostbyname(name); + if (hp == 0) { + if ((*result)->ai_canonname != 0) + /* XXX Indicate success with the existing name? */ + return 0; + /* No canonname listed, and gethostbyname failed. */ + name2 = name; + } else { + /* Sometimes gethostbyname will be directed to /etc/hosts + first, and sometimes that file will have entries with + the unqualified name first. So take the first entry + that looks like it could be a FQDN. */ + for (i = 0; hp->h_aliases[i]; i++) { + if (strchr(hp->h_aliases[i], '.') != 0) { + name2 = hp->h_aliases[i]; + break; + } + } + /* Give up, just use the first name (h_name == + h_aliases[0] on all systems I've seen). */ + if (hp->h_aliases[i] == 0) + name2 = hp->h_name; + } + + (*result)->ai_canonname = strdup(name2); + if ((*result)->ai_canonname == 0) { + (*faiptr)(*result); + *result = 0; + return EAI_MEMORY; + } + } +#endif + +#ifdef _AIX + for (; ai; ai = ai->ai_next) { + /* AIX 4.3.3 libc is broken. It doesn't set the family or len + fields of the sockaddr structures. */ + if (ai->ai_addr->sa_family == 0) + ai->ai_addr->sa_family = ai->ai_family; +#ifdef HAVE_SA_LEN /* always true on aix, actually */ + if (ai->ai_addr->sa_len == 0) + ai->ai_addr->sa_len = ai->ai_addrlen; +#endif + } +#endif + + /* Not dealt with yet: + + - Some versions of GNU libc can lose some IPv4 addresses in + certain cases when multiple IPv4 and IPv6 addresses are + available. + + - Wrapping a possibly-missing system version, as we'll need to + do for Windows. */ + + return 0; +} + +void freeaddrinfo (struct addrinfo *ai) +{ +#ifdef COPY_FIRST_CANONNAME + free(ai->ai_canonname); + ai->ai_canonname = 0; + (*faiptr)(ai); +#else + (*faiptr)(ai); +#endif +} +#endif /* WRAP_GETADDRINFO */ + +#ifdef WRAP_GETNAMEINFO +int getnameinfo (const struct sockaddr *sa, socklen_t len, + char *host, size_t hostlen, + char *service, size_t servicelen, + int flags) +{ + return (*gniptr)(sa, len, host, hostlen, service, servicelen, flags); +} +#endif /* WRAP_GETNAMEINFO */ + +#undef getaddrinfo +#undef getnameinfo +#undef freeaddrinfo +static int (*gaiptr) (const char *, const char *, const struct addrinfo *, + struct addrinfo **) = &getaddrinfo; +static void (*faiptr) (struct addrinfo *) = &freeaddrinfo; +#ifdef WRAP_GETNAMEINFO +static int (*gniptr) (const struct sockaddr *, socklen_t, + char *, size_t, char *, size_t, int) = &getnameinfo; #endif + +#endif /* WRAP_GETADDRINFO || WRAP_GETNAMEINFO */ diff --git a/src/include/fake-addrinfo.h b/src/include/fake-addrinfo.h index d7c651180..661c0ffd0 100644 --- a/src/include/fake-addrinfo.h +++ b/src/include/fake-addrinfo.h @@ -51,13 +51,15 @@ #define FAI_CONCAT(A,B) FAI_CONCAT2(A,B) #define FAI_CONCAT2(A,B) A ## B -/* Various C libraries have broken implementations of getaddrinfo. */ -#undef fixup_addrinfo -#define fixup_addrinfo FAI_CONCAT(FAI_PREFIX, _fixup_addrinfo) +#define fixup_addrinfo do not call me! -extern void fixup_addrinfo (struct addrinfo *ai); +#if defined (__linux__) || defined (_AIX) +/* See comments in fake-addrinfo.c. */ +# define WRAP_GETADDRINFO +/* # define WRAP_GETNAMEINFO */ +#endif -#if !defined (HAVE_GETADDRINFO) || defined (BROKEN_GETADDRINFO) +#if !defined (HAVE_GETADDRINFO) || defined(WRAP_GETADDRINFO) #undef getaddrinfo #define getaddrinfo FAI_CONCAT(FAI_PREFIX, _fake_getaddrinfo) @@ -65,6 +67,11 @@ extern void fixup_addrinfo (struct addrinfo *ai); #define getnameinfo FAI_CONCAT(FAI_PREFIX, _fake_getnameinfo) #undef freeaddrinfo #define freeaddrinfo FAI_CONCAT(FAI_PREFIX, _fake_freeaddrinfo) + +#endif + +#if !defined (HAVE_GETADDRINFO) + #undef gai_strerror #define gai_strerror FAI_CONCAT(FAI_PREFIX, _fake_gai_strerror) #undef addrinfo @@ -141,6 +148,10 @@ struct addrinfo { #undef EAI_SYSTEM #define EAI_SYSTEM 11 +#endif /* ! HAVE_GETADDRINFO */ + +#if !defined (HAVE_GETADDRINFO) || defined (WRAP_GETADDRINFO) + int getaddrinfo (const char *name, const char *serv, const struct addrinfo *hint, struct addrinfo **result); @@ -151,14 +162,18 @@ int getnameinfo (const struct sockaddr *addr, socklen_t len, void freeaddrinfo (struct addrinfo *ai); -char *gai_strerror (int code); +#endif + +#if !defined (HAVE_GETADDRINFO) #define HAVE_FAKE_GETADDRINFO #define HAVE_GETADDRINFO #undef HAVE_GETNAMEINFO #define HAVE_GETNAMEINFO -#endif /* HAVE_GETADDRINFO */ +char *gai_strerror (int code); + +#endif /* Fudge things on older gai implementations. */ /* AIX 4.3.3 is based on RFC 2133; no AI_NUMERICHOST. */ -- 2.26.2