libexplain  1.4.D001
libexplain/buffer/errno/gethostbyname.c
Go to the documentation of this file.
00001 /*
00002  * libexplain - Explain errno values returned by libc functions
00003  * Copyright (C) 2013 Peter Miller
00004  *
00005  * This program is free software; you can redistribute it and/or modify it
00006  * under the terms of the GNU Lesser General Public License as published by
00007  * the Free Software Foundation; either version 3 of the License, or (at
00008  * your option) any later version.
00009  *
00010  * This program is distributed in the hope that it will be useful, but
00011  * WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
00013  * General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU Lesser General Public License
00016  * along with this program. If not, see <http://www.gnu.org/licenses/>.
00017  */
00018 
00019 #include <libexplain/ac/errno.h>
00020 #include <libexplain/ac/string.h>
00021 
00022 #include <libexplain/buffer/errno/generic.h>
00023 #include <libexplain/buffer/errno/gethostbyname.h>
00024 #include <libexplain/buffer/gettext.h>
00025 #include <libexplain/explanation.h>
00026 
00027 
00028 static void
00029 explain_buffer_errno_gethostbyname_system_call(explain_string_buffer_t *sb,
00030     int errnum, const char *name)
00031 {
00032     (void)errnum;
00033     explain_string_buffer_puts(sb, "gethostbyname(name = ");
00034     explain_string_buffer_puts_quoted(sb, name);
00035     explain_string_buffer_putc(sb, ')');
00036 }
00037 
00038 
00039 /* RFC 2181 */
00040 #define ALLOW_UNDERSCORE 1
00041 #define ALLOW_LEADING_UNDERSCORE 1
00042 
00043 /* RFC 1123 */
00044 #define ALLOW_LEADING_HYPHEN 1
00045 #define ALLOW_LEADING_DIGIT 1
00046 #define ALLOW_ALL_NUMERIC 1
00047 
00048 
00049 typedef struct label label;
00050 struct label
00051 {
00052     const char      *begin;
00053     size_t          size;
00054     const char      *end;
00055 };
00056 
00057 
00067 static int
00068 valid_label(const label *lp)
00069 {
00070     const char      *name;
00071     size_t          size;
00072     size_t          seen_alpha;
00073     size_t          seen_digit;
00074     size_t          seen_punct;
00075 
00076     /*
00077      * Each label must be between 1 and 63 characters long.
00078      */
00079     size = lp->size;
00080     if (size < 1 || size > 63)
00081         return 0;
00082 
00083     /*
00084      * The original specification of hostnames in RFC 952, mandated that
00085      * labels could not start with a digit or with a hyphen, and must
00086      * not end with a hyphen.  However, a subsequent specification (RFC
00087      * 1123) permitted hostname labels to start with digits.
00088      *
00089      * That means they still can't start with a hyphen.
00090      */
00091     name = lp->begin;
00092     switch (name[0])
00093     {
00094 #ifndef ALLOW_LEADING_DIGIT
00095     case '0': case '1': case '2': case '3': case '4':
00096     case '5': case '6': case '7': case '8': case '9':
00097         return 0;
00098 #endif
00099 
00100 #ifndef ALLOW_LEADING_HYPHEN
00101     case '-':
00102         return 0;
00103 #endif
00104 
00105 #ifdef ALLOW_UNDERSCORE
00106 #ifndef ALLOW_LEADING_UNDERSCORE
00107     case '_':
00108         /* no *leading* underscore */
00109         return 0;
00110 #endif
00111 #endif
00112 
00113     default:
00114         break;
00115     }
00116 
00117     /*
00118      * Check each character.
00119      */
00120     seen_alpha = 0;
00121     seen_digit = 0;
00122     seen_punct = 0;
00123     while (size-- > 0)
00124     {
00125         unsigned char c = *name++;
00126         switch (c)
00127         {
00128         case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
00129         case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
00130         case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
00131         case 'V': case 'W': case 'X': case 'Y': case 'Z':
00132         case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
00133         case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
00134         case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
00135         case 'v': case 'w': case 'x': case 'y': case 'z':
00136             /*
00137              * Hostname labels may contain only the ASCII letters 'a' through
00138              * 'z' (in a case-insensitive manner), the digits '0' through '9',
00139              * and the hyphen ('-').
00140              */
00141             ++seen_alpha;
00142             break;
00143 
00144         case '0': case '1': case '2': case '3': case '4':
00145         case '5': case '6': case '7': case '8': case '9':
00146             /*
00147              * The original specification of hostnames in RFC 952, mandated that
00148              * labels could not start with a digit or with a hyphen, and must
00149              * not end with a hyphen.  However, a subsequent specification (RFC
00150              * 1123) permitted hostname labels to start with digits.
00151              */
00152             ++seen_digit;
00153             break;
00154 
00155         case '-':
00156             if (size == 0)
00157             {
00158                 /* must not end with a hyphen */
00159                 return 0;
00160             }
00161             ++seen_punct;
00162             break;
00163 
00164 #ifdef ALLOW_UNDERSCORE
00165         case '_':
00166             if (size == 0)
00167             {
00168                 /* must not end with an underscore */
00169                 return 0;
00170             }
00171             ++seen_punct;
00172             break;
00173 #endif
00174 
00175         default:
00176             /*
00177              * No other symbols, punctuation characters, or white space are
00178              * permitted.
00179              */
00180             return 0;
00181         }
00182     }
00183 #ifndef ALLOW_ALL_NUMERIC
00184     if (seen_digit == lp->size)
00185         return 0;
00186 #endif
00187 
00188     /*
00189      * Found nothing wrong.
00190      */
00191     return 1;
00192 }
00193 
00194 
00203 static label *
00204 validate_hostname(const char *name)
00205 {
00206     static label    labels[(255 + 1) / 2];
00207     size_t          nlabels;
00208     size_t          len;
00209     const char      *name_end;
00210     label           *lp;
00211     size_t          j;
00212 
00213     /*
00214      * Hostnames are composed of series of labels concatenated with
00215      * dots, as are all domain names. For example, "en.wikipedia.org"
00216      * is a hostname. Each label must be between 1 and 63 characters
00217      * long, and the entire hostname (including the delimiting dots)
00218      * has a maximum of 255 characters.
00219      *
00220      * The Internet standards (Request for Comments) for protocols
00221      * mandate that component hostname labels may contain only the
00222      * ASCII letters 'a' through 'z' (in a case-insensitive manner),
00223      * the digits '0' through '9', and the hyphen ('-'). The original
00224      * specification of hostnames in RFC 952, mandated that labels
00225      * could not start with a digit or with a hyphen, and must not end
00226      * with a hyphen. However, a subsequent specification (RFC 1123)
00227      * permitted hostname labels to start with digits. No other symbols,
00228      * punctuation characters, or white space are permitted.
00229      *
00230      * [http://en.wikipedia.org/wiki/Hostname]
00231      */
00232     len = strlen(name);
00233     if (len < 1 || len > 255)
00234         return (label *)(-1);
00235     name_end = name + len;
00236 
00237     nlabels = 0;
00238     for (;;)
00239     {
00240         const char      *dot;
00241 
00242         dot = memchr(name, '.', name_end - name);
00243         if (!dot)
00244         {
00245             lp = &labels[nlabels++];
00246             lp->begin = name;
00247             lp->size = name_end - name;
00248             lp->end = name_end;
00249             break;
00250         }
00251         lp = &labels[nlabels++];
00252         lp->begin = name;
00253         lp->size = dot - name;
00254         lp->end = dot;
00255         name = dot + 1;
00256     }
00257 
00258     /*
00259      * If a trailing dot was used, the last "label" will be empty, so toss it.
00260      * No other labels are permitted to be empty.
00261      */
00262     if (nlabels > 0 && labels[nlabels - 1].size == 0)
00263         --nlabels;
00264     if (nlabels == 0)
00265         return (label *)(-1);
00266 
00267     /*
00268      * Check each label for validity.
00269      */
00270     for (j = 0; j < nlabels; ++j)
00271     {
00272         lp = &labels[j];
00273         if (!valid_label(lp))
00274             return lp;
00275     }
00276 
00277     /*
00278      * Couldn't find anything wrong.
00279      */
00280     return 0;
00281 }
00282 
00283 
00284 static int
00285 check_valid_hostname(explain_string_buffer_t *sb, const char *name)
00286 {
00287     label           *lp;
00288     explain_string_buffer_t sb2;
00289     char            buffer[1000];
00290 
00291     lp = validate_hostname(name);
00292     if (!lp)
00293         return 0;
00294     if (sb == sb->footnotes)
00295         explain_string_buffer_puts(sb, "; ");
00296     if (lp == (label *)(-1))
00297     {
00298         explain_buffer_gettext
00299         (
00300             sb,
00301             i18n
00302             (
00303                 /*
00304                  * xgettext: this error message is used to explain a
00305                  * NO_RECOVERY error returned by the gethostbyname system
00306                  * call, in the case where the host name is not a valid
00307                  * length.
00308                  */
00309                 "the host name is not a valid length"
00310             )
00311         );
00312         return 1;
00313     }
00314 
00315     if (lp->size == 0)
00316     {
00317         explain_buffer_gettext
00318         (
00319             sb,
00320 
00321             /*
00322              * xgettext: this error message is used to explain a
00323              * NO_RECOVERY error returned by the gethostbyname
00324              * system call, in the case where the host name contains
00325              * at least one empty label.
00326              */
00327             i18n("host names may not have empty parts")
00328         );
00329         return 1;
00330     }
00331 
00332     explain_string_buffer_init(&sb2, buffer, sizeof(buffer));
00333     explain_string_buffer_puts_quoted_n(&sb2, lp->begin, lp->size);
00334     explain_string_buffer_printf_gettext
00335     (
00336         sb,
00337         i18n
00338         (
00339             /*
00340              * xgettext: this error message is used to explain a NO_RECOVERY
00341              * error returned by the gethostbyname system call, in the case
00342              * where the host name is not properly formed.
00343              *
00344              * %1$s => text of the label (the text between the dots)
00345              */
00346             "the host name (at %s) is not properly formed"
00347         ),
00348         buffer
00349     );
00350     return 1;
00351 }
00352 
00353 
00354 void
00355 explain_buffer_errno_gethostbyname_explanation(explain_string_buffer_t *sb,
00356     int errnum, const char *syscall_name, const char *name)
00357 {
00358     /*
00359     * http://www.opengroup.org/onlinepubs/009695399/functions/gethostbyname.html
00360      *
00361      * In this case, errnum has the h_error value, not the errno value.
00362      */
00363     switch (errnum)
00364     {
00365     case HOST_NOT_FOUND:
00366         /*
00367          * FIXME: what about if it is shorter, e.g. www.example.com vs
00368          * example.com.  We need to let the user know what part of the name is
00369          * not found.
00370          */
00371         explain_buffer_gettext
00372         (
00373             sb,
00374             i18n
00375             (
00376                 /*
00377                  * xgettext: this error message is used to explain
00378                  * HOST_NOT_FOUND errors returned by the gethostbyname system
00379                  * call.  Authoritative Answer Host not found.  The specified
00380                  * host is unknown.
00381                  */
00382                 "an authoritative DNS server was reached and the given host "
00383                 "name does not exist"
00384             )
00385         );
00386         check_valid_hostname(sb->footnotes, name);
00387         break;
00388 
00389     case TRY_AGAIN:
00390         /*
00391          * FIXME: what about if it is shorter, e.g. work1.fixed.example.com
00392          * vs fixed.example.com. vs example.com.  We need to let the user know
00393          * what part of the name is not found.
00394          */
00395         explain_buffer_gettext
00396         (
00397             sb,
00398             i18n
00399             (
00400                 /*
00401                  * xgettext: this error message is used to explain TRY_AGAIN
00402                  * errors returned by the gethostbyname system call.
00403                  * Authoritative Answer Host not found, or SERVERFAIL.  A
00404                  * temporary error occurred on an authoritative name server.
00405                  * The specified host is unknown.
00406                  */
00407                 "an authoritative DNS server could not be reached and so the "
00408                 "given host name does not appear to exist"
00409             )
00410         );
00411         check_valid_hostname(sb->footnotes, name);
00412         /* FIXME: i18n */
00413         explain_string_buffer_puts(sb->footnotes, "try again later");
00414         break;
00415 
00416     case NO_RECOVERY:
00417         /* Non recoverable error: FORMERR */
00418         if (check_valid_hostname(sb, name))
00419             break;
00420 
00421         /* FIXME: which? */
00422         explain_buffer_gettext
00423         (
00424             sb,
00425             i18n
00426             (
00427                 /*
00428                  * xgettext: this error message is used to explain NO_RECOVERY
00429                  * errors returned by the gethostbyname system call.
00430                  * Non recoverable errors, FORMERR, REFUSED, NOTIMP.
00431                  */
00432                 "the operation was refused, or "
00433                 "the operation is not implemented on this system"
00434             )
00435         );
00436         break;
00437 
00438     case NO_DATA:
00439         explain_buffer_gettext
00440         (
00441             sb,
00442             i18n
00443             (
00444                 /*
00445                  * xgettext: this error message is used to explain NO_DATA
00446                  * errors returned by the gethostbyname system call.
00447                  * Valid name, no data record of requested type.
00448                  * The requested name is valid but does not have an IP address.
00449                  */
00450                 "the host name does not have any DNS data"
00451             )
00452         );
00453         check_valid_hostname(sb->footnotes, name);
00454         break;
00455 
00456 #if defined(NO_ADDRESS) && defined(NO_DATA) && NO_ADDRESS != NO_DATA
00457     case NO_ADDRESS:
00458         explain_buffer_gettext
00459         (
00460             sb,
00461             i18n
00462             (
00463                 /*
00464                  * xgettext: this error message is used to explain NO_ADDRESS
00465                  * errors returned by the gethostbyname system call.
00466                  * No address, look for MX record.
00467                  */
00468                 "the host name has DNS data but does not have an IP address"
00469             )
00470         );
00471         explain_buffer_puts(sb->footnote, "look for an MX record");
00472         check_value_hostname(sb->footnotes, name);
00473         break;
00474 #endif
00475 
00476 #ifdef NETDB_INTERNAL
00477     case NETDB_INTERNAL:
00478 #endif
00479     default:
00480         explain_buffer_errno_generic(sb, errno, syscall_name);
00481         break;
00482     }
00483 }
00484 
00485 
00486 void
00487 explain_buffer_errno_gethostbyname(explain_string_buffer_t *sb, int errnum,
00488     const char *name)
00489 {
00490     explain_explanation_t exp;
00491     int                   hold_errno;
00492 
00493     hold_errno = errno;
00494     explain_explanation_init(&exp, errnum);
00495     explain_buffer_errno_gethostbyname_system_call
00496     (
00497         &exp.system_call_sb,
00498         errnum,
00499         name
00500     );
00501     explain_buffer_errno_gethostbyname_explanation
00502     (
00503         &exp.explanation_sb,
00504         errnum,
00505         "gethostbyname",
00506         name
00507     );
00508     errno = hold_errno; /* for NETDB_INTERNAL error case */
00509     explain_explanation_assemble_netdb(&exp, sb);
00510 }
00511 
00512 
00513 /* vim: set ts=8 sw=4 et : */