/*++

Copyright (c) 1995-2001 Microsoft Corporation

Module Name:

    name.c

Abstract:

    Domain Name System (DNS) Library

    DNS name routines.

Author:

    Jim Gilroy (jamesg)     October 1995

Revision History:

    jamesg  Jan 1997    UTF-8, Unicode conversions

--*/


#include "local.h"


//
//  DNS name cannonicalization
//
//  Flags to form cannonical name.
//

#define DNS_CANONICALIZING_FLAGS        ( LCMAP_LOWERCASE )

//
//  Comparison flags -- args to compare string
//
//  These flags are what DS uses when calling CompareString.
//  They are defined in ntdsapi.h.
//
//  Note:  these NORM_IGNOREXYZ flags which directory uses
//  for compare, actually STOP downcasing of these in LCMapString
//  -- international folks need to give us the correct deal here
//
//  We will only use the IGNORECASE flag because this is the
//  only one which we can use in LCMapString().
//  We want to say that if two names compare equal that you
//  can register either one and lookup the other and get the
//  result.  In other words they are equal throughout the
//  client-server system.
//  

#if 0
#define DS_DEFAULT_LOCALE_COMPARE_FLAGS    (NORM_IGNORECASE     |   \
                                            NORM_IGNOREKANATYPE |   \
                                            NORM_IGNORENONSPACE |   \
                                            NORM_IGNOREWIDTH)
#endif


#define DNS_CANONICAL_COMPARE_FLAGS     ( NORM_IGNORECASE )


//
//  Locale to canonically downcase in.
//
//  Need to disambiguate to a universal standard so that every DNS
//  server interprets these the same way.
//
//  In Win2K we used US English.  
//      Sublang: US English (0x04)  Lang:  English (0x09)
//      (note sublang US english actually 0x1, but sublang starts at
//      bit 10)
//
//  #define DNS_CANONICAL_LOCALE      ( 0x0409 )
//
//  For Whistler invariant locale is created;  It is actually the
//  same as US English for downcasing -- US English has no
//  exceptions to the default case conversion table.
//

#define DNS_CANONICAL_LOCALE      ( LOCALE_INVARIANT )




//
//  DNS Character properties for validation
//
//  DCR:  combine char validation and file tables
//      Probably could be combined with file character
//      lookup, by simply merging bit fields appropriately.
//      At this point time, however, no need to disturb
//      file lookup, which is working fine.
//
//  Character attributes bitfields
//

#define B_RFC                   0x00000001
#define B_NUMBER                0x00000002
#define B_UPPER                 0x00000004
#define B_NON_RFC               0x00000008

#define B_UTF8_TRAIL            0x00000010
#define B_UTF8_FIRST_TWO        0x00000020
#define B_UTF8_FIRST_THREE      0x00000040
#define B_UTF8_PAIR             0x00000080

#define B_DOT                   0x00000800
#define B_SPECIAL               0x00001000
#define B_LEADING_ONLY          0x00004000


//
//  Generic characters
//

#define DC_RFC          (B_RFC)
#define DC_LOWER        (B_RFC)
#define DC_UPPER        (B_UPPER | B_RFC)
#define DC_NUMBER       (B_NUMBER | B_RFC)
#define DC_NON_RFC      (B_NON_RFC)

#define DC_UTF8_TRAIL   (B_UTF8_TRAIL)
#define DC_UTF8_1ST_2   (B_UTF8_FIRST_TWO)
#define DC_UTF8_1ST_3   (B_UTF8_FIRST_THREE)
#define DC_UTF8_PAIR    (B_UTF8_PAIR)

//
//  Special characters
//      * valid as single label wildcard
//      _ leading SRV record domain names
//      / in classless in-addr
//

#define DC_DOT          (B_SPECIAL | B_DOT)

#define DC_ASTERISK     (B_SPECIAL | B_LEADING_ONLY)

#define DC_UNDERSCORE   (B_SPECIAL | B_LEADING_ONLY)

#define DC_BACKSLASH    (B_SPECIAL)

//
//  More special
//  These have no special validations, but have special file
//  properties, so define to keep table in shape for merge with
//  file chars.
//

#define DC_NULL         (0)

#define DC_OCTAL        (B_NON_RFC)
#define DC_RETURN       (B_NON_RFC)
#define DC_NEWLINE      (B_NON_RFC)
#define DC_TAB          (B_NON_RFC)
#define DC_BLANK        (B_NON_RFC)
#define DC_QUOTE        (B_NON_RFC)
#define DC_SLASH        (B_NON_RFC)
#define DC_OPEN_PAREN   (B_NON_RFC)
#define DC_CLOSE_PAREN  (B_NON_RFC)
#define DC_COMMENT      (B_NON_RFC)



//
//  DNS character table
//
//  These routines handle the name conversion issues relating to
//  writing names and strings in flat ANSI files
//      -- special file characters
//      -- quoted string
//      -- character quotes for special characters and unprintable chars
//
//  The character to char properties table allows simple mapping of
//  a character to its properties saving us a bunch of compare\branch
//  instructions in parsing file names\strings.
//
//  See nameutil.h for specific properties.
//

DWORD    DnsCharPropertyTable[] =
{
    //  control chars 0-31 must be octal in all circumstances
    //  end-of-line and tab characters are special

    DC_NULL,                // zero special on read, some RPC strings NULL terminated

    DC_OCTAL,   DC_OCTAL,   DC_OCTAL,   DC_OCTAL,
    DC_OCTAL,   DC_OCTAL,   DC_OCTAL,   DC_OCTAL,

    DC_TAB,                 // tab
    DC_NEWLINE,             // line feed
    DC_OCTAL,
    DC_OCTAL,
    DC_RETURN,              // carriage return
    DC_OCTAL,
    DC_OCTAL,

    DC_OCTAL,   DC_OCTAL,   DC_OCTAL,   DC_OCTAL,
    DC_OCTAL,   DC_OCTAL,   DC_OCTAL,   DC_OCTAL,
    DC_OCTAL,   DC_OCTAL,   DC_OCTAL,   DC_OCTAL,
    DC_OCTAL,   DC_OCTAL,   DC_OCTAL,   DC_OCTAL,

    DC_BLANK,               // blank, special char but needs octal quote

    DC_NON_RFC,             // !
    DC_QUOTE,               // " always must be quoted
    DC_NON_RFC,             // #
    DC_NON_RFC,             // $
    DC_NON_RFC,             // %
    DC_NON_RFC,             // &
    DC_NON_RFC,             // '

    DC_OPEN_PAREN,          // ( datafile line extension
    DC_CLOSE_PAREN,         // ) datafile line extension
    DC_ASTERISK,            // *
    DC_NON_RFC,             // +
    DC_NON_RFC,             // ,
    DC_RFC,                 // - RFC for hostname
    DC_DOT,                 // . must quote in names
    DC_BACKSLASH,           // /

    // 0 - 9 RFC for hostname

    DC_NUMBER,  DC_NUMBER,  DC_NUMBER,  DC_NUMBER,
    DC_NUMBER,  DC_NUMBER,  DC_NUMBER,  DC_NUMBER,
    DC_NUMBER,  DC_NUMBER,

    DC_NON_RFC,             // :
    DC_COMMENT,             // ;  datafile comment
    DC_NON_RFC,             // <
    DC_NON_RFC,             // =
    DC_NON_RFC,             // >
    DC_NON_RFC,             // ?
    DC_NON_RFC,             // @

    // A - Z RFC for hostname

    DC_UPPER,   DC_UPPER,   DC_UPPER,   DC_UPPER,
    DC_UPPER,   DC_UPPER,   DC_UPPER,   DC_UPPER,
    DC_UPPER,   DC_UPPER,   DC_UPPER,   DC_UPPER,
    DC_UPPER,   DC_UPPER,   DC_UPPER,   DC_UPPER,
    DC_UPPER,   DC_UPPER,   DC_UPPER,   DC_UPPER,
    DC_UPPER,   DC_UPPER,   DC_UPPER,   DC_UPPER,
    DC_UPPER,   DC_UPPER,

    DC_NON_RFC,             // [
    DC_SLASH,               // \ always must be quoted
    DC_NON_RFC,             // ]
    DC_NON_RFC,             // ^
    DC_UNDERSCORE,          // _
    DC_NON_RFC,             // `

    // a - z RFC for hostname

    DC_LOWER,   DC_LOWER,   DC_LOWER,   DC_LOWER,
    DC_LOWER,   DC_LOWER,   DC_LOWER,   DC_LOWER,
    DC_LOWER,   DC_LOWER,   DC_LOWER,   DC_LOWER,
    DC_LOWER,   DC_LOWER,   DC_LOWER,   DC_LOWER,
    DC_LOWER,   DC_LOWER,   DC_LOWER,   DC_LOWER,
    DC_LOWER,   DC_LOWER,   DC_LOWER,   DC_LOWER,
    DC_LOWER,   DC_LOWER,

    DC_NON_RFC,             // {
    DC_NON_RFC,             // |
    DC_NON_RFC,             // }
    DC_NON_RFC,             // ~
    DC_OCTAL,               // 0x7f DEL code

    //  UTF8 trail bytes
    //      - chars   0x80 <= X < 0xc0
    //      - mask [10xx xxxx]
    //
    //  Lead UTF8 character determines count of bytes in conversion.
    //  Trail characters fill out conversion.

    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,

    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,
    DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,  DC_UTF8_TRAIL,

    //  UTF8_1ST_OF_2
    //      - chars > 0xc0 to 0xdf
    //      - mask [110x xxxx]
    //
    //  Converting unicode chars > 7 bits <= 11 bits (from 0x80 to 0x7ff)
    //  consists of first of two char followed by one trail bytes

    DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,
    DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,
    DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,
    DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,
    DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,
    DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,
    DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,
    DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,  DC_UTF8_1ST_2,

    //  UTF8_1ST_OF_3
    //      - chars > 0xe0
    //      - mask [1110 xxxx]
    //
    //  Converting unicode > 11 bits (0x7ff)
    //  consists of first of three char followed by two trail bytes

    DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,
    DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,
    DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,
    DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,
    DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,
    DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,
    DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,
    DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3,  DC_UTF8_1ST_3
};



VOID
Dns_VerifyValidFileCharPropertyTable(
    VOID
    )
/*++

Routine Description:

    Verify haven't broken lookup table.

Arguments:

    None

Return Value:

    None

--*/
{
    ASSERT( DnsCharPropertyTable[0]       == DC_NULL        );
    ASSERT( DnsCharPropertyTable['\t']    == DC_TAB         );
    ASSERT( DnsCharPropertyTable['\n']    == DC_NEWLINE     );
    ASSERT( DnsCharPropertyTable['\r']    == DC_RETURN      );
    ASSERT( DnsCharPropertyTable[' ']     == DC_BLANK       );
    ASSERT( DnsCharPropertyTable['"']     == DC_QUOTE       );
    ASSERT( DnsCharPropertyTable['(']     == DC_OPEN_PAREN  );
    ASSERT( DnsCharPropertyTable[')']     == DC_CLOSE_PAREN );
    ASSERT( DnsCharPropertyTable['*']     == DC_ASTERISK     );
    ASSERT( DnsCharPropertyTable['-']     == DC_RFC         );
    ASSERT( DnsCharPropertyTable['.']     == DC_DOT         );
    ASSERT( DnsCharPropertyTable['/']     == DC_BACKSLASH   );
    ASSERT( DnsCharPropertyTable['0']     == DC_NUMBER      );
    ASSERT( DnsCharPropertyTable['9']     == DC_NUMBER      );
    ASSERT( DnsCharPropertyTable[';']     == DC_COMMENT     );
    ASSERT( DnsCharPropertyTable['A']     == DC_UPPER       );
    ASSERT( DnsCharPropertyTable['Z']     == DC_UPPER       );
    ASSERT( DnsCharPropertyTable['\\']    == DC_SLASH       );
    ASSERT( DnsCharPropertyTable['_']     == DC_UNDERSCORE  );
    ASSERT( DnsCharPropertyTable['a']     == DC_LOWER       );
    ASSERT( DnsCharPropertyTable['z']     == DC_LOWER       );
    ASSERT( DnsCharPropertyTable[0x7f]    == DC_OCTAL       );
    ASSERT( DnsCharPropertyTable[0x80]    == DC_UTF8_TRAIL  );
    ASSERT( DnsCharPropertyTable[0xbf]    == DC_UTF8_TRAIL  );
    ASSERT( DnsCharPropertyTable[0xc0]    == DC_UTF8_1ST_2  );
    ASSERT( DnsCharPropertyTable[0xdf]    == DC_UTF8_1ST_2  );
    ASSERT( DnsCharPropertyTable[0xe0]    == DC_UTF8_1ST_3  );
    ASSERT( DnsCharPropertyTable[0xff]    == DC_UTF8_1ST_3  );
};



//
//  Validation routine flags
//

#define DNSVAL_ALLOW_LEADING_UNDERSCORE     0x00010000
#define DNSVAL_ALLOW_ASTERISK               0x00020000
#define DNSVAL_ALLOW_BACKSLASH              0x00040000

//
//  Validation bit flags
//

#define DNS_BIT_NAME_FQDN                   0x00000001
#define DNS_BIT_NAME_SINGLE_LABEL           0x00000002
#define DNS_BIT_NAME_DOTTED                 0x00000004
#define DNS_BIT_NAME_ROOT                   0x00000008

#define DNS_BIT_NAME_CONTAINS_UPPER         0x00000010
#define DNS_BIT_NAME_NUMERIC                0x00000100
#define DNS_BIT_NAME_NUMERIC_LABEL          0x00000200
#define DNS_BIT_NAME_NUMERIC_FIRST_LABEL    0x00000400

#define DNS_BIT_NAME_UNDERSCORE             0x00001000
#define DNS_BIT_NAME_WILDCARD               0x00002000
#define DNS_BIT_NAME_BACKSLASH              0x00004000
#define DNS_BIT_NAME_NON_RFC_ASCII          0x00008000
#define DNS_BIT_NAME_MULTIBYTE              0x00010000
#define DNS_BIT_NAME_BINARY_LABEL           0x00020000

#define DNS_BIT_NAME_INVALID                0x80000000




#if 0
//
//  Old validation -- retired
//
//  Downcase and validation table
//
//  DCR:  table lookup for all DNS char properties
//        especially RFC, non-RFC, invalid
//

typedef struct _Dns_ValidationChar
{
    CHAR        chDown;
    UCHAR       fNonRfc;
}
DNS_VALIDATION_CHAR;

#define NON_RFC         (1)
#define EXTENDED_CHAR   (0x80)


DNS_VALIDATION_CHAR
Dns_ValidateDowncaseChar(
    IN      CHAR            ch
    )
/*++

Routine Description:

    Validates character

Arguments:

    ch  -- character to validate

Return Value:

    Validation character -- downcased character and flag

--*/
{
    DNS_VALIDATION_CHAR     val;

    //  default to normal character

    val.chDown = ch;
    val.fNonRfc = 0;

    //
    //  break out character tests
    //      - attempt most likely to least likely
    //      - but also working down to simplify tests
    //

    if ( (UCHAR)ch >= 'a' )
    {
        if ( (UCHAR)ch <= 'z' )
        {
            return( val );
        }
        val.fNonRfc = NON_RFC;
        if ( ch & 0x80 )
        {
            val.fNonRfc = EXTENDED_CHAR;
        }
    }

    else if ( (UCHAR)ch >= 'A' )
    {
        if ( (UCHAR)ch <= 'Z' )
        {
            val.chDown = ch + 0x20;
            return( val );
        }
        val.fNonRfc = NON_RFC;
    }

    else if ( (UCHAR)ch >= '0' )
    {
        if ( (UCHAR)ch <= '9' )
        {
            return( val );
        }
        val.fNonRfc = NON_RFC;
    }

    else if ( (UCHAR)ch > ' ' )
    {
        if ( (UCHAR)ch == '-' )
        {
            return( val );
        }
        val.fNonRfc = NON_RFC;
    }

    //  blank or below is flat error

    else
    {
        val.chDown = 0;
        val.fNonRfc = NON_RFC;
    }

    return( val );
}
#endif



//
//  Name validation
//
//  DCR:  name validation by bitfield
//
//  An interesting approach to validation, would be to expose
//  a set of properties about a name.
//  Caller could then specify allowable set (we'd give packages)
//  and we'd give back actual set.
//  Low level routines would do nothing but return bit field of
//  property set.
//
//  Properties would include:
//      - RFC
//      - contains numeric
//      - contains upper
//      - all numeric
//      - first label numeric
//
//      - utf8 multibyte
//      - underscore
//      - other non-RFC
//      - unprintable
//      - non-utf8 high (i.e. requires binary label)
//
//      - FQDN
//      - single label
//      - root
//


DNS_STATUS
validateDnsNamePrivate(
    IN      LPCSTR          pszName,
    IN      DWORD           dwFlag,
    OUT     PDWORD          pLabelCount,
    OUT     PDWORD          pResultFlag
    )
/*++

Routine Description:

    Verifies name is valid DNS name.

Arguments:

    pszName -- DNS name (standard dotted form) to check

    dwFlags -- validation flags
        - DNSVAL_ALLOW_LEADING_UNDERSCORE
        - DNSVAL_ALLOW_BACKSLASH
        - DNSVAL_ALLOW_ASTERIK

    pLabelCount -- addr to recv label count

    pResultFlag -- addr to recv result flag

Return Value:

    ERROR_SUCCESS               -- completely RFC compliant name
    DNS_ERROR_NON_RFC_NAME      -- syntax valid, but not standard RFC name
    DNS_ERROR_INVALID_NAME_CHAR -- syntax valid, but invalid characters
    ERROR_INVALID_NAME          -- name completely useless, bogus, toast

--*/
{
    PUCHAR      pch = (PUCHAR)pszName;
    UCHAR       ch;
    DWORD       charProp;
    DWORD       labelCount = 0;
    DWORD       trailCount = 0;
    INT         labelCharCount = 0;
    INT         labelNumberCount = 0;
    DWORD       flag;
    BOOL        fqdn = FALSE;
    BOOL        fnonRfc = FALSE;
    BOOL        finvalidChar = FALSE;
    BOOL        fnameNonNumeric = FALSE;
    BOOL        flabelNonNumeric = FALSE;
    DNS_STATUS  status;


    DNSDBG( TRACE, ( "validateNamePrivate()\n" ));

    if ( !pch )
    {
        goto InvalidName;
    }

    //
    //  validations
    //      - name length (255)
    //      - label length (63)
    //      - UTF8 encoding correct
    //      - no unprintable characters
    //

    while ( 1 )
    {
        //  get next character and properties

        ch = *pch++;
        charProp = DnsCharPropertyTable[ ch ];

        //  inc label count
        //      - do here for simplicity, dec in "." case below

        labelCharCount++;

        //
        //  simplify UTF8 -- just get it out of the way
        //      need to do first or else need trailCount==0 checks
        //      on all other paths
        //

        if ( ch >= 0x80 )
        {
            DWORD tempStatus;

            tempStatus = Dns_ValidateUtf8Byte(
                            ch,
                            & trailCount );
            if ( tempStatus != ERROR_SUCCESS )
            {
                DNSDBG( READ, (
                    "ERROR:  Name UTF8 trail count check at %c\n", ch ));
                goto InvalidName;
            }
            fnonRfc = TRUE;
            continue;
        }

        //
        //  trail count check
        //      - all ASCII chars, must not be in middle of UTF8
        //

        if ( trailCount )
        {
            DNSDBG( READ, (
                "ERROR:  Name failed trail count check at %c\n", ch ));
            goto InvalidName;
        }

        //
        //  full RFC -- continue
        //

        if ( charProp & B_RFC )
        {
            if ( charProp & B_NUMBER )
            {
                labelNumberCount++;
            }
            continue;
        }

        //
        //  label termination:  dot or NULL
        //

        if ( ch == '.' || ch == 0 )
        {
            labelCharCount--;

            //  FQDN termination
            //      - termination with no bytes in label
            //
            //  two cases:
            //  1) terminate on NULL char
            //      - standard FQDN "foo.bar."
            //      - but empty name invalid
            //  2) terminate on dot
            //      - only "." root valid
            //      - all other ".." or ".xyz" cases invalid

            if ( labelCharCount == 0 )
            {
                fqdn = TRUE;

                if ( ch == 0 )
                {
                    if ( labelCount )
                    {
                        goto Done;
                    }
                }
                else if ( pch == pszName+1 && *pch == 0 )
                {
                    //  root
                    //      - set flags for validity
                    //      - skip final length check

                    fnameNonNumeric = TRUE;
                    flabelNonNumeric = TRUE;
                    goto DoneRoot;
                }
                DNSDBG( READ, (
                    "ERROR:  Name (%s) failed check\n",
                    pszName ));
                goto InvalidName;
            }

            //
            //  read non-empty label
            //      - label length validity
            //      - detect non-numeric labels
            //      (easier to handle numeric name check, by detecting non-numeric)
            //

            if ( labelCharCount > DNS_MAX_LABEL_LENGTH )
            {
                DNSDBG( READ, ( "ERROR:  Name failed label count check\n" ));
                goto InvalidName;
            }

            if ( labelNumberCount != labelCharCount )
            {
                fnameNonNumeric = TRUE;
                if ( labelCount == 0 )
                {
                    flabelNonNumeric = TRUE;
                }
            }

            //  count label
            //      - stop if NULL terminator
            //      - otherwise, reset for next label and continue

            labelCount++;
            if ( ch == 0 )
            {
                break;
            }
            labelCharCount = 0;
            labelNumberCount = 0;
            continue;
        }

        //
        //  non-RFC
        //      - currently accepting only "_" as allowable as part of
        //      microsoft acceptable non-RFC set
        //
        //      however DNS server must be able to read *, \, etc
        //      it gets called through Dns_CreateStandardDnsName()
        //
        //  note, could tighten this up with special flag, but since
        //  this only speeds case with invalid chars, there's not much
        //  point;  underscore is likely to see significant use
        //

        //  underscore
        //      - can be valid as part of SRV domain name
        //      - otherwise non-RFC

        if ( ch == '_' )
        {
            if ( labelCharCount == 1 &&
                 (*pch && *pch!= '.') &&
                 (dwFlag & DNSVAL_ALLOW_LEADING_UNDERSCORE) )
            {
                continue;
            }
            fnonRfc = TRUE;
            continue;
        }

        //  backslash
        //      - used to denote classless in-addr domains
        //      - so valid even as zone name on server
        //      - otherwise completely invalid

        else if ( ch == '/' )
        {
            if ( dwFlag & DNSVAL_ALLOW_BACKSLASH )
            {
                continue;
            }
        }

        //  asterisk
        //      - valid only as single-byte first label in wildcard name
        //      - otherwise completely invalid

        else if ( ch == '*' )
        {
            if ( labelCount == 0 &&
                labelCharCount == 1 &&
                 ( *pch==0 || *pch=='.') &&
                 (dwFlag & DNSVAL_ALLOW_ASTERISK) )
            {
                continue;
            }
        }

        //  anything else is complete junk
        //
        //  JENHANCE:  if desired, could break out printable\non

        fnonRfc = TRUE;
        finvalidChar = TRUE;
        DNSDBG( READ, ( "ERROR:  Name character %c failed check\n", ch ));
        continue;
    }

Done:

    //  verify total name length
    //  to fit in wire 255 limit:
    //      - FQDN can be up to 254
    //      - non-FQDN can be up to 253

    pch--;
    DNS_ASSERT( pch > pszName );
    labelCharCount = (INT)(pch - pszName);

    if ( !fqdn )
    {
        labelCharCount++;
    }
    if ( labelCharCount >= DNS_MAX_NAME_LENGTH )
    {
        DNSDBG( READ, ( "ERROR:  Name failed final length check\n" ));
        goto InvalidName;
    }

DoneRoot:

    //
    //  return flags
    //
    //  JENHANCE:  all returns from validateNamePrivate() could come
    //      as result flag;   then charset issues could be separated
    //      out by higher level routine
    //

    *pLabelCount = labelCount;

    flag = 0;
    if ( fqdn )
    {
        flag |= DNS_BIT_NAME_FQDN;
    }
    if ( ! fnameNonNumeric )
    {
        flag |= DNS_BIT_NAME_NUMERIC;
    }
    if ( ! flabelNonNumeric )
    {
        flag |= DNS_BIT_NAME_NUMERIC_FIRST_LABEL;
    }
    *pResultFlag = flag;

    //
    //  return status
    //      ERROR_SUCCESS -- full RFC name
    //      DNS_ERROR_NON_RFC_NAME -- MS extended and '_' names
    //      DNS_ERROR_INVALID_NAME_CHAR -- syntaxtically valid, but bad chars
    //      ERROR_INVALID_NAME -- syntaxtically invalid name
    //

    status = ERROR_SUCCESS;

    if ( finvalidChar )
    {
        status = DNS_ERROR_INVALID_NAME_CHAR;
    }
    else if ( fnonRfc )
    {
        status = DNS_ERROR_NON_RFC_NAME;
    }

    DNSDBG( READ, (
        "Leave validateNamePrivate(), status = %d\n",
        status ));

    return( status );


InvalidName:

    DNSDBG( READ, (
        "Leave validateNamePrivate(), status = ERROR_INVALID_NAME\n" ));

    *pLabelCount = 0;
    *pResultFlag = 0;
    return( ERROR_INVALID_NAME );
}



DNS_STATUS
Dns_ValidateName_UTF8(
    IN      LPCSTR          pszName,
    IN      DNS_NAME_FORMAT Format
    )
/*++

Routine Description:

    Verifies name is valid DNS name.

Arguments:

    pszName -- DNS name (standard dotted form) to check

    Format -- required format of DNS name

Return Value:

    ERROR_SUCCESS               -- completely RFC compliant name
    DNS_ERROR_NON_RFC_NAME      -- syntax valid, but not standard RFC name
    DNS_ERROR_NUMERIC_NAME      -- syntax valid, but numeric label violation
    DNS_ERROR_INVALID_NAME_CHAR -- syntax valid, but invalid characters
    ERROR_INVALID_NAME          -- name completely useless, bogus, toast

--*/
{
    DNS_STATUS  status;
    DWORD       labelCount;
    BOOL        isFqdn;
    DWORD       flag = 0;
    DWORD       resultFlag = 0;


    DNSDBG( TRACE, (
        "Dns_ValidateName_UTF8()\n"
        "\tname     = %s\n"
        "\tformat   = %d\n",
        pszName,
        Format
        ));

    if ( !pszName )
    {
        return( ERROR_INVALID_NAME );
    }

    //
    //  special casing?
    //
    //  SRV records can have leading underscores
    //  wildcards can have first label "*"
    //  backslash ok in classless in-addr domains
    //

    switch( Format )
    {
#if 0
    case DnsNameServerZonePrivate:

        flag = DNSVAL_ALLOW_BACKSLASH | DNSVAL_ALLOW_LEADING_UNDERSCORE;
#endif

    case DnsNameWildcard:

        flag = DNSVAL_ALLOW_ASTERISK;
        break;

    case DnsNameSrvRecord:

        flag = DNSVAL_ALLOW_LEADING_UNDERSCORE;
        break;
    }

    //
    //  do validation
    //
    //  return immediately on invalid name, so type
    //  specific returns do not overwrite this error
    //

    status = validateDnsNamePrivate(
                pszName,
                flag,
                & labelCount,
                & resultFlag
                );
    if ( status == ERROR_INVALID_NAME )
    {
        return( status );
    }

    //
    //  do name type specific validation
    //

    switch( Format )
    {
    //  domain name -- any valid non-numeric DNS name

    case DnsNameDomain:

        if ( resultFlag & DNS_BIT_NAME_NUMERIC )
        {
            return( DNS_ERROR_NUMERIC_NAME );
        }
        return( status );

    //  domain name label -- any valid single-label DNS name

    case DnsNameDomainLabel:

        if ( labelCount != 1 || resultFlag & DNS_BIT_NAME_FQDN )
        {
            return( ERROR_INVALID_NAME );
        }
        return( status );

    //  hostname full -- non-numeric hostname label

    case DnsNameHostnameFull:

        if ( resultFlag & DNS_BIT_NAME_NUMERIC_FIRST_LABEL )
        {
            return( DNS_ERROR_NUMERIC_NAME );
        }
        return( status );

    //  hostname label -- single label and non-numeric

    case DnsNameHostnameLabel:

        if ( labelCount != 1 || resultFlag & DNS_BIT_NAME_FQDN )
        {
            return( ERROR_INVALID_NAME );
        }
        if ( resultFlag & DNS_BIT_NAME_NUMERIC_FIRST_LABEL )
        {
            return( DNS_ERROR_NUMERIC_NAME );
        }
        return( status );

    //
    //  wildcard -- single "*" as first label
    //      if *.???? then must revalidate the rest of the name as
    //          "*" has probably resulted in validation error
    //      if "*" then consider this successful
    //

    case DnsNameWildcard:

        if ( *pszName == '*' )
        {
            return( status );
        }
        return( ERROR_INVALID_NAME );

    //
    //  SRV label -- validate leading underscore
    //

    case DnsNameSrvRecord:

        if ( *pszName == '_' )
        {
            return( status );
        }
        return( ERROR_INVALID_NAME );

    //
    //  unknown format validation
    //

    default:

        return( ERROR_INVALID_PARAMETER );
    }
}


DNS_STATUS
Dns_ValidateName_W(
    IN      LPCWSTR         pwszName,
    IN      DNS_NAME_FORMAT Format
    )
/*++

Routine Description:

    Verifies name is valid DNS name.

Arguments:

    pwszName -- DNS name (standard dotted form) to check

    Format -- required format of DNS name

Return Value:

    ERROR_SUCCESS -- if completely compliant name
    DNS_ERROR_NON_RFC_NAME -- if not standard RFC name
    ERROR_INVALID_NAME -- if name completely useless, bogus, toast

--*/
{
    DWORD   nameLength = MAX_PATH;
    CHAR    nameBuffer[ MAX_PATH ] = {0};   // init for prefix

    //
    //  convert name to UTF8
    //      - if can't convert, then can't fit into buffer
    //      so must be invalid name on length grounds
    //

    if ( ! Dns_NameCopy(
                nameBuffer,
                & nameLength,       // avail buf length
                (PCHAR) pwszName,
                0,                  // unknown length
                DnsCharSetUnicode,  // unicode in
                DnsCharSetUtf8      // UTF8 out
                ) )
    {
        return( ERROR_INVALID_NAME );
    }

    //
    //  validate name in UTF8 format

    return Dns_ValidateName_UTF8(
                (LPCSTR) nameBuffer,
                Format );
}



DNS_STATUS
Dns_ValidateName_A(
    IN      LPCSTR          pszName,
    IN      DNS_NAME_FORMAT Format
    )
/*++

Routine Description:

    Verifies name is valid DNS name.

Arguments:

    pszName -- DNS name (standard dotted form) to check

    Format -- required format of DNS name

Return Value:

    ERROR_SUCCESS -- if completely compliant name
    DNS_ERROR_NON_RFC_NAME -- if not standard RFC name
    ERROR_INVALID_NAME -- if name completely useless, bogus, toast

--*/
{
    DWORD   nameLength = MAX_PATH;
    CHAR    nameBuffer[ MAX_PATH ];

    //
    //  convert name to UTF8
    //      - if can't convert, then can't fit into buffer
    //      so must be invalid name on length grounds
    //

    if ( ! Dns_NameCopy(
                nameBuffer,
                & nameLength,       // avail buf length
                (PCHAR) pszName,
                0,                  // unknown length
                DnsCharSetAnsi,     // unicode in
                DnsCharSetUtf8      // UTF8 out
                ) )
    {
        return( ERROR_INVALID_NAME );
    }

    //
    //  validate name in UTF8 format

    return Dns_ValidateName_UTF8(
                (LPCSTR) nameBuffer,
                Format );
}



DNS_STATUS
Dns_ValidateDnsString_UTF8(
    IN      LPCSTR          pszString
    )
/*++

Routine Description:

    Verifies string is valid DNS string.

Arguments:

    pszString -- DNS string (standard dotted form) to check

Return Value:

    ERROR_SUCCESS -- if completely compliant string
    ERROR_INVALID_DATA -- otherwise

--*/
{
    PUCHAR      pch = (PUCHAR) pszString;
    UCHAR       ch;
    DWORD       trailCount = 0;

    DNSDBG( TRACE, ( "Dns_ValidateDnsString_UTF8()\n" ));

    if ( !pszString )
    {
        return( ERROR_INVALID_DATA );
    }

    //
    //  validations
    //      - string length (255)
    //      - UTF8 chars valid
    //      - no unprintable characters
    //

    while ( ch = *pch++ )
    {
        if ( ch & 0x80 )
        {
            DWORD status;
            status = Dns_ValidateUtf8Byte(
                        ch,
                        & trailCount );
            if ( status != ERROR_SUCCESS )
            {
                return( status );
            }
        }

        else if ( ch < ' ' )
        {
            return( ERROR_INVALID_DATA );
        }
    }

    //  verify string length ok

    if ( pch - pszString > DNS_MAX_NAME_LENGTH )
    {
        return( ERROR_INVALID_DATA );
    }
    return( ERROR_SUCCESS );
}



DNS_STATUS
Dns_ValidateDnsString_W(
    IN      LPCWSTR     pszString
    )
/*++

Routine Description:

    Verifies string is valid DNS string.

    Not sure there's any need to UNICODE string routine.

Arguments:

    pszString -- DNS string

Return Value:

    ERROR_SUCCESS -- if completely compliant string
    ERROR_INVALID_DATA -- otherwise

--*/
{
    INT     count;
    CHAR    stringUtf8[ DNS_MAX_NAME_BUFFER_LENGTH ];
    DWORD   bufLength = DNS_MAX_NAME_BUFFER_LENGTH;

    DNSDBG( TRACE, ( "Dns_ValidateDnsString_W()\n" ));

    if ( !pszString )
    {
        return( ERROR_INVALID_DATA );
    }

    //
    //  need to convert to unicode in order to test UTF8 (wire) length
    //      - buffer (twice max length) can hold any valid
    //      coversion of unicode name within max length
    //

    count = wcslen( pszString );
    if ( count > DNS_MAX_NAME_LENGTH )
    {
        return( ERROR_INVALID_DATA );
    }

    //
    //  convert, then test
    //

    if ( ! Dns_StringCopy(
                stringUtf8,
                & bufLength,
                (LPSTR) pszString,
                (WORD) count,
                DnsCharSetUnicode,  // unicode in
                DnsCharSetUtf8      // UTF8 out
                ) )
    {
        return( ERROR_INVALID_DATA );
    }

    return  Dns_ValidateDnsString_UTF8( stringUtf8 );
}



//
//  Name cannonicalization
//
//  Currently, clients downcase (when extended) in their locale to go to wire.
//  On server end however all names are cannonicalized.
//
//  DCR:  cannonicalize completely on client end?
//      Ideally client would do complete cannonicallization on its end.
//      The only issue is whether there are locale specific issues where
//      downcasing would be different and yield substaintially different result
//

#define MAX_DNS_DOWN_CASE_BUF_LEN 512



DWORD
Dns_MakeCanonicalNameW(
    OUT     PWSTR           pBuffer,
    IN      DWORD           BufLength,
    IN      PWSTR           pwsString,
    IN      DWORD           StringLength
    )
/*++

Routine Description:

    Create cannonical unicode DNS name.

    This name is downcased and ambiguities converted to standard
    DNS characters.

Arguments:

    pBuffer -- buffer to recv canon name

    BufLength -- length of buffer;  if 0, buffer MUST have adequate length

    pwsString -- ptr to string to copy

    StringLength -- string length, if known

Return Value:

    Count of characters converted INCLUDING NULL terminator.
    Zero on error.

--*/
{
    DWORD   inLength = StringLength;

    //
    //  verify adequate buffer length
    //
    //  DCR:  should allow non-null terminated canonicalizations?
    //
    //  note:  we allow and convert non-null terminated name
    //      the result will not necessarily be NULL terminated
    //      if buffer is exactly equal to string length
    //

    if ( inLength == 0 )
    {
        inLength = wcslen( pwsString );
        inLength++;
    }

    if ( BufLength < inLength )
    {
        DNSDBG( ANY, (
            "ERROR:  insufficient cannon buffer len = %d\n"
            "\tstring = %S, len = %d\n",
            BufLength,
            pwsString,
            inLength ));
        SetLastError( ERROR_INSUFFICIENT_BUFFER );
        return( 0 );
    }

    //
    //  convert name
    //      - downcase with canonicalizing rules
    //

    inLength = LCMapStringW(
                    DNS_CANONICAL_LOCALE,
                    DNS_CANONICALIZING_FLAGS,
                    pwsString,
                    inLength,
                    pBuffer,
                    BufLength
                    );

#if DBG
    if ( inLength == 0 )
    {
        DNS_STATUS status = GetLastError();

        DNSDBG( ANY, (
            "Canonicalization failed => %d\n"
            "\tin  %S\n",
            status,
            pwsString ));

        SetLastError( status );
    }
    else
    {
        //
        //  DCR:  warning this print can blow on non-null terminated conversions
        //

        DNSDBG( READ, (
            "Canonicalized name at %p\n"
            "\tin   %S\n"
            "\tout  %S\n",
            pwsString,
            pwsString,
            (PWSTR) pBuffer ));
    }
#endif

    return( inLength );
}



DWORD
Dns_MakeCanonicalNameInPlaceW(
    IN      PWCHAR          pwString,
    IN      DWORD           StringLength
    )
/*++

Routine Description:

    In place cannonicalization of name.

Arguments:

    pwString -- ptr to string to copy

    StringLength -- length of string
        if zero string assumed to be NULL terminated, in this case
        canonicalization includes NULL terminator

Return Value:

    Count of characters converted -- including NULL terminator if
        StringLength is unspecified
    Zero on error.

--*/
{
    DWORD   nameLength = StringLength;
    WCHAR   tempBuffer[ DNS_MAX_NAME_BUFFER_LENGTH_UNICODE ] = { 0 };    // init for prefix

    DNSDBG( READ, (
        "Dns_MakeCanonicalNameInPlace()\n"
        "\tpwString = %S\n"
        "\tlength   = %d\n",
        pwString,
        StringLength ));

    //  if length unknown, must be NULL terminated string

    if ( nameLength == 0 )
    {
        nameLength = (DWORD) wcslen( pwString );
        nameLength++;
    }

    //
    //  cannonicalize (downcase and cleanup)
    //      - copy string to temp buffer
    //      - then cannonicalize into original buffer
    //

    if ( nameLength <= DNS_MAX_NAME_BUFFER_LENGTH_UNICODE )
    {
        wcsncpy( tempBuffer, pwString, nameLength );

        return  Dns_MakeCanonicalNameW(
                    pwString,       //  write back to original string
                    nameLength,     //  length of buffer
                    tempBuffer,     //  input is temp copy
                    nameLength      //  input length
                    );
    }

    return  0;
}



INT
Dns_DowncaseNameLabel(
    OUT     PCHAR           pchResult,
    //OUT     PDWORD          pNameProperty,
    IN      PCHAR           pchLabel,
    IN      DWORD           cchLabel,
    IN      DWORD           dwFlag
    )
/*++

Routine Description:

    Create a downcased version of DNS name.

    This is UTF8 only routine for use by DNS server to
    validate and downcase label during node creation.

Arguments:

    pchResult -- resulting downcased label;
        MUST have MAX_LABEL_BUFFER_LENGTH

    //pNameProperty -- name properties of result
    //    ResultLength -- ptr to DWORD to recieve resulting length

    pchLabel -- ptr to label

    cchLabel -- count of bytes in label

    dwFlag -- flag indicating what names are acceptable
        strict RFC      => DNS_ALLOW_RFC_NAMES_ONLY
        non RFC names   => DNS_ALLOW_NONRFC_NAMES
        UTF8 extended   => DNS_ALLOW_MULTIBYTE_NAMES
        anything        => DNS_ALLOW_ALL_NAMES

Return Value:

    If extended name -- length of converted name.
    Zero if success.
    (-1) on error.

--*/
{
    UCHAR       ch;
    PUCHAR      pchout = pchResult;
    PUCHAR      pch = pchLabel;
    DWORD       count = cchLabel;
    DWORD       charProp;
    DWORD       trailCount = 0;
    DWORD       property = 0;


    DNSDBG( TRACE, (
        "Dns_DowncaseNameLabel( %.*s )\n"
        "\tflag = %08x\n",
        cchLabel,
        pchLabel,
        dwFlag ));

    if ( count == 0  ||  count > DNS_MAX_LABEL_LENGTH )
    {
        goto InvalidName;
    }

    //
    //  copy each character
    //      - downcasing upper case chars
    //      - detecting invalid chars (unprintable, blank, dot)
    //

    while ( count-- )
    {
        //  get next character and properties

        ch = *pch++;
        *pchout++ = ch;
        charProp = DnsCharPropertyTable[ ch ];

        //  trail count check
        //      check this first to avoid trail count check on all
        //      other char types
        //
        //  DEVNOTE:  note this screens binary labels

        if ( trailCount )
        {
            if ( (charProp & B_UTF8_TRAIL) )
            {
                trailCount--;
                continue;
            }

            DNSDBG( READ, (
                "ERROR:  Name failed trail count check at %c\n", ch ));
            property |= DNS_BIT_NAME_BINARY_LABEL;
        }

        //  full RFC
        //      - map upper case to lower case
        //      - continue

        if ( charProp & B_RFC )
        {
            if ( charProp & B_UPPER )
            {
                --pchout;
                *pchout++ = ch + 0x20;
            }
            continue;
        }

        //
        //  check for extended chars
        //      - trail characters should have been caught above
        //      - doing this first so can make single trailCount
        //      check for all other ASCII chars

        if ( ch >= 0x80 )
        {
            DWORD tempStatus;
            tempStatus = Dns_ValidateUtf8Byte(
                            ch,
                            & trailCount );
            if ( tempStatus != ERROR_SUCCESS )
            {
                DNSDBG( READ, (
                    "ERROR:  Name UTF8 trail count check at %c\n", ch ));
                goto InvalidName;
            }
            property |= DNS_BIT_NAME_MULTIBYTE;
            continue;
        }

        //
        //  non-RFC
        //      - currently accepting only "_" as allowable as part of
        //      microsoft acceptable non-RFC set
        //
        //      however DNS server must be able to read *, \, etc
        //      as these can be part of valid label
        //
        //  note, could tighten this up with special flag, but since
        //  this only speeds case with invalid chars, there's not much
        //  point;  underscore is likely to see significant use
        //

        //  underscore
        //      - can be valid as leading label as part of SRV domain name
        //      - otherwise non-RFC

        if ( ch == '_' )
        {
            if ( count == cchLabel - 1 )
            {
                continue;
            }
            property |= DNS_BIT_NAME_UNDERSCORE;
            continue;
        }

        //  backslash
        //      - used to denote classless in-addr domains
        //          must have leading and following chars
        //      - otherwise completely invalid

        else if ( ch == '/' )
        {
            if ( count != 0 && count != cchLabel-1 )
            {
                continue;
            }
        }

        //  asterisk
        //      - valid only as single-byte first label in wildcard name
        //      - otherwise completely invalid

        else if ( ch == '*' )
        {
            if ( count == 0 )
            {
                continue;
            }
        }

        //  anything else is complete junk
        //  currently only acceptable if allow binary labels
        //
        //  JENHANCE:  could break out non-RFC (printable\non)

        property |= DNS_BIT_NAME_BINARY_LABEL;
        DNSDBG( READ, ( "ERROR:  Name character %c failed check\n", ch ));
        continue;
    }

    //
    //  fill out name properties
    //
    //  JENHANCE:  full property fill out
    //
    //  currently only property we're returning is multibyte name issue
    //  as that's all the server needs to check
    //
    //  if save more properties then test becomes something like this
    //  if ( (property & dwFlags) != (property & SUPPORTED_CHECK_FLAGS) )
    //

#if 0
    //*pNameProperty = property;

    if ( (property & dwFlags) != property )
    {
        goto InvalidName;
    }

    if ( property & DNS_BIT_NAME_MULTIBYTE )
    {
        goto Extended;
    }
#endif

    //  standard RFC name -- skip the detail parsing

    if ( property == 0 )
    {
        goto Done;
    }

    //  other chars invalid unless allowing all

    if ( property & DNS_BIT_NAME_BINARY_LABEL )
    {
        if ( dwFlag != DNS_ALLOW_ALL_NAMES )
        {
            goto InvalidName;
        }
    }

    //  multibyte
    //      - do extended downcase if multibyte
    //      - do nothing if binary
    //      - for strict this is invalid

    if ( property & DNS_BIT_NAME_MULTIBYTE )
    {
        if ( dwFlag == DNS_ALLOW_MULTIBYTE_NAMES ||
            dwFlag == DNS_ALLOW_ALL_NAMES )
        {
            goto Extended;
        }
#if 0
        if ( dwFlag != DNS_BINARY_LABELS )
        {
            goto InvalidName;
        }
#endif
        goto InvalidName;
    }

    //  underscore valid unless completely strict

    if ( property & DNS_BIT_NAME_UNDERSCORE )
    {
        if ( dwFlag == DNS_ALLOW_RFC_NAMES_ONLY )
        {
            goto InvalidName;
        }
    }

Done:

    //
    //  NULL terminate, return success.
    //

    *pchout = 0;
    return( 0 );


Extended:

    //
    //  DCR:  better approach to extended names
    //      1) cannonicalize upfront
    //          - do whole name in one pass
    //          - no need to upcase here, similar to validateName() routine
    //      2) cannonicalize here
    //          - detect extended
    //          - cannonicalize here
    //          - single recursion into routine like validateName()
    //

    //
    //  extended character encountered
    //      - convert to unicode
    //      - downcase
    //      - convert back to UTF8
    //

    //
    //  DCR_PERF:  optimize for names where extended already downcased
    //
    //  DCR_PERF:  should wrap this code into UTF8 cannon routine
    //


    //if ( ! (dwFlags & DNS_ALLOW_ALREADY_EXTENDED_DOWN) )
    {
        DWORD   length;
        WCHAR   unicodeString[ DNS_MAX_LABEL_BUFFER_LENGTH ];
#if DBG
        WCHAR   originalName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
        WCHAR   downName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
        WCHAR   cannonDownName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
#endif

        DNSDBG( READ, (
            "Extended character encountered downcasing string %*.s\n"
            "\tconverting to unicode for case conversion\n",
            cchLabel,
            pchLabel ));

        length = Dns_Utf8ToUnicode(
                     pchResult,
                     cchLabel,
                     unicodeString,
                     DNS_MAX_LABEL_BUFFER_LENGTH
                     );
        DNSDBG( READ, (
            "Unicode converted string %.*S\n"
            "\tlength = %d\n"
            "\tlast error = %d\n",
            length,
            unicodeString,
            length,
            GetLastError() ));

        if ( length == 0 )
        {
            DNSDBG( READ, (
                "Rejecting invalid UTF8 string %.*s\n"
                "\tFailed conversion to unicode OR conversion created\n"
                "\tinvalid unicode string\n",
                cchLabel,
                pchResult ));
            goto InvalidName;
        }

        //  no possible conversion of valid length UTF8, can
        //  overflow unicode buffer

        ASSERT( length <= DNS_MAX_LABEL_LENGTH );

        Dns_MakeCanonicalNameInPlaceW( unicodeString, length );
#if 1
        DNSDBG( READ, (
            "Canonical unicode name %.*S\n"
            "\tlength = %d\n",
            length,
            unicodeString,
            length ));
#endif

        //
        //  reconvert to UTF8
        //      - mapping to UTF8 is just math, so only error
        //      is possibly overflowing UTF8 max label buffer
        //      - catch this error is character count changes
        //      note, that also must catch case where write fills
        //      64 byte buffer eliminating NULL terminator
        //

        length = Dns_UnicodeToUtf8(
                     unicodeString,
                     length,
                     pchResult,
                     DNS_MAX_LABEL_BUFFER_LENGTH
                     );
        DNSDBG( READ, (
            "UTF8 downcased string %.*s\n"
            "\tlength = %d\n",
            length,
            pchResult,
            length ));

        if ( length != cchLabel )
        {
            DNSDBG( ANY, (
                "Downcasing UTF8 label %.*s, changed character count!\n"
                "\tfrom %d to %d\n"
                "\tResult name %.*s\n"
                "\tlast error = %d\n",
                cchLabel,
                pchLabel,
                cchLabel,
                length,
                length,
                pchResult,
                GetLastError() ));

            if ( length == 0 || length > DNS_MAX_LABEL_LENGTH )
            {
                DNSDBG( ANY, (
                    "Failed conversion of downcased unicode string %S\n"
                    "\tback into UTF8.\n",
                    unicodeString ));
                goto InvalidName;
            }
        }

        //
        //  NULL terminate, return length to indicate extended name
        //

        pchResult[ length ] = 0;
        return( (INT)length );
    }

    // no UTF8 multi-byte allowed -- drop through to invalid name return


InvalidName:

    //  return (-1) for error

    DNSDBG( READ, (
        "Dns_DowncaseNameLabel() found label to be invalid.\n"
        "\tlabel        = %.*s\n"
        "\tcount        = %d\n"
        "\tproperty     = %08x\n",
        cchLabel,
        pchLabel,
        count,
        property ));

    return( -1 );
}



LPSTR
Dns_CreateStandardDnsNameCopy(
    IN      PCHAR           pchName,
    IN      DWORD           cchName,
    IN      DWORD           dwFlag
    )
/*++

Routine Description:

    Makes copy of DNS name in valid "standard form"
        - downcased
        - no trailing dot (to avoid confusing DS)

Arguments:

    pchName -- ptr DNS name in UTF8

    cchName -- count of chars in name;  may be NULL

    dwFlag  -- strict checking flags;  currently ignored

Return Value:

    Ptr to copy of DNS name.
    NULL on invalid name.

--*/
{
    PCHAR       pszcopy = NULL;
    DNS_STATUS  status;
    DWORD       length;

    DNSDBG( TRACE, ( "Dns_CreateStandardDnsName()\n" ));
    DNSDBG( READ, (
        "Dns_CreateStandardDnsName()\n"
        "\tpchName = %.*s\n"
        "\tcchName = %d\n",
        cchName,
        pchName,
        cchName ));

    if ( !pchName )
    {
        status = ERROR_INVALID_NAME;
        goto Failed;
    }

    //
    //  ASCII string?
    //

    if ( Dns_IsStringAsciiEx( pchName, cchName ) )
    {
        //
        //  make copy
        //

        pszcopy = Dns_CreateStringCopy( pchName, cchName );
        if ( !pszcopy )
        {
            status = DNS_ERROR_NO_MEMORY;
            goto Failed;
        }

        //
        //  validate, check against strict criteria
        //
        //  no validation until relax criteria
        //
        //  DCR:  name validation within Dns_CreateStandardNameCopy()
        //      accept anything except INVALID_NAME
        //      flags return FQDN info
        //
#if 0
        status = Dns_ValidateName_UTF8( pszcopy, DnsNameDomain );
        if ( status == ERROR_INVALID_NAME )
        {
            goto Failed;
        }
#endif
        //
        //  downcase
        //  remove any trailing dot, except for root name
        //

        _strlwr( pszcopy );
        length = strlen( pszcopy );
        if ( length == 0 )
        {
            status = ERROR_INVALID_NAME;
            goto Failed;
        }
        length--;
        if ( length > 0 && pszcopy[length] == '.' )
        {
            pszcopy[length] = 0;
        }

        DNSDBG( READ, (
            "Standard DNS name copy of %.*s is %s\n",
            cchName,
            pchName,
            pszcopy ));
        return( pszcopy );
    }

    //
    //  unicode name
    //

    else
    {
        WCHAR   unicodeName[ DNS_MAX_NAME_BUFFER_LENGTH ];
        WCHAR   cannonicalName[ DNS_MAX_NAME_BUFFER_LENGTH ];
        DWORD   unicodeBufferLength;

        //
        //  convert to unicode
        //      - buf length is in bytes
        //

        unicodeBufferLength = DNS_MAX_NAME_BUFFER_LENGTH * 2;

        length = Dns_NameCopy(
                    (PSTR) unicodeName,
                    & unicodeBufferLength,
                    pchName,
                    cchName,
                    DnsCharSetUtf8,
                    DnsCharSetUnicode
                    );
        if ( length == 0 )
        {
            DNSDBG( ANY, (
                "ERROR conversion of name %.*s to unicode failed!\n",
                cchName,
                pchName ));
            status = ERROR_INVALID_NAME;
            goto Failed;
        }

        //
        //  make cannonical name
        //      - buf length is in unicode characters
        //      - output length is in unicode chars

        length = Dns_MakeCanonicalNameW(
                    cannonicalName,
                    length / 2,
                    unicodeName,
                    dwFlag );

        ASSERT( length != 0 );
        if ( length == 0 )
        {
            status = ERROR_INVALID_NAME;
            goto Failed;
        }

        //
        //  allocate UTF8 converted copy
        //      - this conversion should never fail
        //      - string length is unicode chars
        //

        pszcopy = Dns_StringCopyAllocate(
                    (PSTR) cannonicalName,
                    length,
                    DnsCharSetUnicode,      // unicode in
                    DnsCharSetUtf8          // UTF8 out
                    );
        if ( !pszcopy )
        {
            status = DNS_ERROR_NO_MEMORY;
            goto Failed;
        }

        //
        //  validate, check against strict criteria
        //
        //  no validation until relax criteria
        //
        //  DCR:  name validation within Dns_CreateStandardNameCopy()
        //      accept anything except INVALID_NAME
        //      flags return FQDN info
        //
#if 0
        status = Dns_ValidateName_UTF8( pszcopy, DnsNameDomain );
        if ( status == ERROR_INVALID_NAME )
        {
            goto Failed;
        }
#endif
        return( pszcopy );
    }

Failed:

    FREE_HEAP( pszcopy );
    SetLastError( status );
    return( NULL );
}



//
//  Public compare functions
//

#if DNSWIN95

//
//  Running 9x - exposing for debug
//

#define WIN9X_FLAG_START_VAL    (0x1111)

BOOL    g_fWin9x = WIN9X_FLAG_START_VAL;


BOOL
Dns_IsWin9x(
    VOID
    )
/*++

Routine Description:

    Are we on Win9x

Arguments:

    None

Return Value:

    TRUE if on Win9x
    FALSE otherwise

--*/
{
    //
    //  if not yet queried, query for system version
    //  Win9x (and Win3.1) have high bit set, regardless of major version
    //
    //  for Win9x set locale to system default, as we'll use the CRT
    //  functions to do name comparisons (CompareStringW() is a non-implemented
    //  stub on Win9x
    //      - LC_ALL sets for all CRT functions
    //      - "" sets to system default code page
    //

    if ( g_fWin9x == WIN9X_FLAG_START_VAL )
    {
        if ( GetVersion() & 0x80000000 )
        {
            g_fWin9x = TRUE;
            setlocale( LC_ALL, "" );
        }
        else
        {
            g_fWin9x = FALSE;
        }
    }

    return( g_fWin9x );
}
#endif



BOOL
Dns_NameCompare_A(
    IN      PCSTR           pName1,
    IN      PCSTR           pName2
    )
/*++

Routine Description:

    Compare two DNS names.

    Can not use stricmp() because of the possiblity of names with
    trailing dots.

Arguments:

    pName1 - ptr to first DNS name string (dotted format)
    pName2 - ptr to second DNS name string (dotted format)

Return Value:

    TRUE if names equal.
    FALSE otherwise.

--*/
{
    INT len1;
    INT len2;
    INT result;

    //
    //  flat out match
    //      - this is possible with owner names and possibly other fields
    //

    if ( pName1 == pName2 )
    {
        return( TRUE );
    }

    if ( !pName1 || !pName2 )
    {
        return( FALSE );
    }

    //
    //  if lengths NOT equal, then
    //  they must be within one and longer string must have trailing dot
    //      - in this case save
    //

    len1 = strlen( pName1 );
    len2 = strlen( pName2 );

    if ( len2 != len1 )
    {
        if ( len2 == len1+1 )
        {
            if ( pName2[len1] != '.' )
            {
                return( FALSE );
            }
            //  len1 is comparable length
        }
        else if ( len2+1 == len1 )
        {
            if ( pName1[len2] != '.' )
            {
                return( FALSE );
            }
            //  len1 is set to comparable length
            len1 = len2;
        }
        else
        {
            return( FALSE );
        }
    }

    //
    //  compare only comparable length of string
    //

    result = CompareStringA(
                //LOCALE_SYSTEM_DEFAULT,
                DNS_CANONICAL_LOCALE,
                DNS_CANONICAL_COMPARE_FLAGS,
                pName1,
                len1,
                pName2,
                len1 );

    if ( result == CSTR_EQUAL )
    {
        return( TRUE );
    }

    //  not equal or error

    return( FALSE );
}



BOOL
Dns_NameCompare_W(
    IN      PCWSTR          pName1,
    IN      PCWSTR          pName2
    )
/*++

Routine Description:

    Compare two (Wide) DNS names.

    Note:  this is unicode aware, it assumes names in WCHAR string
    format. Can not use stricmp() because of the possiblity of names
    with trailing dots.

Arguments:

    pName1 - ptr to first DNS name string (dotted format)
    pName2 - ptr to second DNS name string (dotted format)

Return Value:

    TRUE if names equal.
    FALSE otherwise.

--*/
{
    INT len1;
    INT len2;
    INT result;

    //
    //  flat out match
    //      - this is possible with owner names and possibly other fields
    //

    if ( pName1 == pName2 )
    {
        return( TRUE );
    }

    if ( !pName1 || !pName2 )
    {
        return( FALSE );
    }

    //
    //  if lengths NOT equal, then
    //  they must be within one and longer string must have trailing dot
    //      - in this case save
    //

    len1 = wcslen( pName1 );
    len2 = wcslen( pName2 );

    if ( len2 != len1 )
    {
        if ( len2 == len1+1 )
        {
            if ( pName2[len1] != L'.' )
            {
                return( FALSE );
            }
            //  len1 is comparable length
        }
        else if ( len2+1 == len1 )
        {
            if ( pName1[len2] != L'.' )
            {
                return( FALSE );
            }
            //  len1 is set to comparable length
            len1 = len2;
        }
        else
        {
            return( FALSE );
        }
    }

    //
    //  compare only comparable length of string
    //

#if DNSWIN95
    //
    //  Win9x does not currently support CompareStringW()
    //

    if ( Dns_IsWin9x() )
    {
        return( !_wcsnicmp( pName1, pName2, len1 ) );
    }
#endif

    result = CompareStringW(
                //LOCALE_SYSTEM_DEFAULT,
                DNS_CANONICAL_LOCALE,
                DNS_CANONICAL_COMPARE_FLAGS,
                pName1,
                len1,
                pName2,
                len1 );

    if ( result == CSTR_EQUAL )
    {
        return( TRUE );
    }

    //  not equal or error

    return( FALSE );
}



BOOL
Dns_NameCompare_UTF8(
    IN      PCSTR           pName1,
    IN      PCSTR           pName2
    )
/*++

Routine Description:

    Compare two DNS names.

Arguments:

    pName1 - ptr to first DNS name string (dotted format)
    pName2 - ptr to second DNS name string (dotted format)

Return Value:

    TRUE if names equal.
    FALSE otherwise.

--*/
{
    WCHAR   nameBuffer1[ DNS_MAX_NAME_BUFFER_LENGTH ];
    WCHAR   nameBuffer2[ DNS_MAX_NAME_BUFFER_LENGTH ];
    DWORD   bufLen;

    //
    //  flat out match
    //      - this is possible with owner names and possibly other fields
    //

    if ( pName1 == pName2 )
    {
        return( TRUE );
    }

    if ( !pName1 || !pName2 )
    {
        return( FALSE );
    }

    //
    //  if strings pure ASCII, then use ANSI version
    //

    if ( Dns_IsStringAscii( (PCHAR)pName1 ) &&
         Dns_IsStringAscii( (PCHAR)pName2 ) )
    {
        return Dns_NameCompare_A( pName1, pName2 );
    }

    //
    //  otherwise must take names back to unicode to compare
    //

    bufLen = DNS_MAX_NAME_LENGTH;

    if ( ! Dns_NameCopy(
                (PCHAR) nameBuffer1,
                & bufLen,
                (PCHAR) pName1,
                0,              // length unknown
                DnsCharSetUtf8,
                DnsCharSetUnicode
                ) )
    {
        return( FALSE );
    }

    bufLen = DNS_MAX_NAME_LENGTH;

    if ( ! Dns_NameCopy(
                (PCHAR) nameBuffer2,
                & bufLen,
                (PCHAR) pName2,
                0,              // length unknown
                DnsCharSetUtf8,
                DnsCharSetUnicode
                ) )
    {
        return( FALSE );
    }

    return Dns_NameCompare_W( nameBuffer1, nameBuffer2 );
}



BOOL
Dns_NameComparePrivate(
    IN      PCSTR           pName1,
    IN      PCSTR           pName2,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Compare two DNS names.

    This is simply helpful utility to avoid coding the wide\narrow
    test in the code in a hundred different places.

Arguments:

    pName1 - ptr to first DNS name string (dotted format)
    pName2 - ptr to second DNS name string (dotted format)

Return Value:

    TRUE if names equal.
    FALSE otherwise.

--*/
{
    if ( CharSet == DnsCharSetUnicode )
    {
        return Dns_NameCompare_W(
                    (PCWSTR) pName1,
                    (PCWSTR) pName2 );
    }
    else if ( CharSet == DnsCharSetAnsi )
    {
        return Dns_NameCompare_A(
                    pName1,
                    pName2 );
    }
    else
    {
        return Dns_NameCompare_UTF8(
                    pName1,
                    pName2 );
    }
}



//
//  Advanced name comparison
//  Includes hierarchial name relationship.
//

DNS_NAME_COMPARE_STATUS
Dns_NameCompareEx(
    IN      PCSTR           pszNameLeft,
    IN      PCSTR           pszNameRight,
    IN      DWORD           dwReserved,
    IN      DNS_CHARSET     CharSet
    )
/*++

Routine Description:

    Advanced compare of DNS names, including hierarchial relationship.

Arguments:

    pszNameLeft -- left name

    pszNameRight -- right name

    dwReserved -- reserved for future use (type of comparison)

    CharSet -- char set of names

Return Value:

    DnsNameCompareInvalid       -- one of the names was invalid
    DnsNameCompareEqual         -- names are equal
    DnsNameCompareLeftParent    -- left is ancestor of right name
    DnsNameCompareRightParent   -- right is ancestor of left name
    DnsNameCompareNotEqual      -- name not equal, no hierarchial relationship

--*/
{
    DNS_NAME_COMPARE_STATUS result;
    DNS_STATUS  status;
    INT         lengthLeft;
    INT         lengthRight;
    INT         lengthDiff;
    INT         compareResult;
    DWORD       bufLength;
    WCHAR       nameLeft[ DNS_MAX_NAME_BUFFER_LENGTH ];
    WCHAR       nameRight[ DNS_MAX_NAME_BUFFER_LENGTH ];


    DNSDBG( TRACE, (
        "Dns_NameCompareEx( %s%S, %s%S )\n",
        (CharSet==DnsCharSetUnicode) ? "" : pszNameLeft,
        (CharSet==DnsCharSetUnicode) ? pszNameLeft : "",
        (CharSet==DnsCharSetUnicode) ? "" : pszNameRight,
        (CharSet==DnsCharSetUnicode) ? pszNameRight : ""
        ));

    //
    //  implementation note
    //  there's a lot of inefficiency here, because there are
    //  two different character sets required for
    //      validation -- UTF8 to check packet limits
    //      downcasing\comparison -- unicode for case insensitivity
    //
    //  obviously there are much more efficient paths through this
    //  morass for particular special cases (ASCII names:  downcase
    //  in ANSI, validate, compare);  but since this is not perf
    //  path code we'll take the approach
    //      - convert to unicode
    //      - validate (which will convert at copy to UTF8)
    //      - downcase unicode
    //      - compare unicode
    //

    //
    //  validate args
    //

    if ( ! pszNameLeft || ! pszNameRight )
    {
         goto Invalid;
    }

    //
    //  copy convert to unicode
    //      - downcasing and compare will be done in unicode
    //      - return lengths are bytes converted, convert to string lengths
    //      - Dns_NameCopy() returns zero for invalid convert
    //

    bufLength = DNS_MAX_NAME_BUFFER_LENGTH * 2;

    lengthLeft = (INT) Dns_NameCopy(
                            (PCHAR) nameLeft,
                            & bufLength,
                            (LPSTR) pszNameLeft,
                            0,                     // string NULL terminated
                            CharSet,               // char set in
                            DnsCharSetUnicode      // unicode out
                            );
    if ( lengthLeft == 0 )
    {
        goto Invalid;
    }
    lengthLeft = (lengthLeft/2) - 1;
    ASSERT( lengthLeft >= 0 );

    bufLength = DNS_MAX_NAME_BUFFER_LENGTH * 2;

    lengthRight = (INT) Dns_NameCopy(
                            (PCHAR) nameRight,
                            & bufLength,
                            (LPSTR) pszNameRight,
                            0,                     // string NULL terminated
                            CharSet,               // char set in
                            DnsCharSetUnicode      // unicode out
                            );
    if ( lengthRight == 0 )
    {
        goto Invalid;
    }
    lengthRight = (lengthRight/2) - 1;
    ASSERT( lengthRight >= 0 );

    //
    //  cannonicalize names
    //

    Dns_MakeCanonicalNameInPlaceW( nameLeft, lengthLeft );
    Dns_MakeCanonicalNameInPlaceW( nameRight, lengthRight );

    //
    //  validate names
    //      - must screen empty string or we can fault below
    //

    status = Dns_ValidateName_W( nameLeft, DnsNameDomain );
    if ( ERROR_SUCCESS != status &&
         DNS_ERROR_NON_RFC_NAME != status )
    {
        goto Invalid;
    }

    status = Dns_ValidateName_W( nameRight, DnsNameDomain );
    if ( ERROR_SUCCESS != status &&
         DNS_ERROR_NON_RFC_NAME != status )
    {
        goto Invalid;
    }

    //
    //  add trailing dots
    //
    //  we need to either add or remove trailing dots to make comparisons
    //  the advantage of adding them is that then, the root name does
    //  not require any special casing -- the root is the ancestor of
    //  every name
    //

    if ( nameLeft[ lengthLeft-1 ] != (WORD)'.')
    {
        nameLeft[ lengthLeft++ ]    = (WORD) '.';
        nameLeft[ lengthLeft ]      = (WORD) 0;
    }
    if ( nameRight[ lengthRight-1 ] != (WORD)'.')
    {
        nameRight[ lengthRight++ ]  = (WORD) '.';
        nameRight[ lengthRight ]    = (WORD) 0;
    }

    //
    //  compare equal length strings
    //

    result = DnsNameCompareNotEqual;

    lengthDiff = (INT)lengthLeft - (INT)lengthRight;

    if ( lengthLeft == lengthRight )
    {
        compareResult = wcscmp( nameLeft, nameRight );
        if ( compareResult == 0 )
        {
            result = DnsNameCompareEqual;
        }
        goto Done;
    }

    //
    //  strings not equal
    //      - compare smaller string of length X
    //      to last X characters of larger string
    //      - also must make sure starting at label boundary
    //
    //      note: strstr() is NOT useful for this work, because it
    //      compares useless for this work because it is finding the
    //      first match -- a little thought would indicate that this
    //      will fail in several obvious cases
    //

    //  right string longer
    //      - need to sign change diff to make it offset in right string

    else if ( lengthDiff < 0 )
    {
        lengthDiff = -lengthDiff;

        if ( nameRight[ lengthDiff-1 ] != L'.' )
        {
            goto Done;
        }
        compareResult = wcscmp( nameLeft, nameRight+lengthDiff );
        if ( compareResult == 0 )
        {
            result = DnsNameCompareLeftParent;
        }
        goto Done;
    }

    //  left string longer
    //      - lengthDiff is offset into left string to start compare

    else
    {
        if ( nameLeft[ lengthDiff-1 ] != L'.' )
        {
            goto Done;
        }
        compareResult = wcscmp( nameLeft+lengthDiff, nameRight );
        if ( compareResult == 0 )
        {
            result = DnsNameCompareRightParent;
        }
        goto Done;
    }

Done:

    DNSDBG( TRACE, (
        "Leave DnsNameCompareEx() result = %d\n",
        result ));

    return( result );

Invalid:

    DNSDBG( ANY, (
        "ERROR:  Invalid name to Dns_NameCompareEx()\n" ));

    return( DnsNameCompareInvalid );
}



//
//  Random name utilities
//

PCHAR
_fastcall
Dns_GetDomainName(
    IN      PCSTR           pszName
    )
/*++

Routine Description:

    Get domain name of DNS name.

    Note, this assumes name already in UTF-8

Arguments:

    pszName - standard dotted DNS name

Return Value:

    Ptr to domain name of pszName.
    NULL if pszName is in root domain.

--*/
{
    CHAR    ch;

    //
    //  find next "." in name, then return ptr to next character
    //

    while( ch = *pszName++ )
    {
        if ( ch == '.' )
        {
            if ( *pszName )
            {
                return( (PCHAR)pszName );
            }
            return( NULL );
        }
    }
    return( NULL );
}


PWSTR
_fastcall
Dns_GetDomainName_W(
    IN      PCWSTR          pwsName
    )
{
    PWSTR  pdomain;

    //
    //  find next "." in name, then return ptr to next character
    //

    pdomain = wcschr( pwsName, L'.' );

    if ( pdomain && *(++pdomain) )
    {
        return( pdomain );
    }
    return  NULL;
}



PCHAR
_fastcall
Dns_GetTldForName(
    IN      PCSTR           pszName
    )
/*++

Routine Description:

    Get domain name of DNS name.

    Note, this assumes name already in UTF-8

Arguments:

    pszName - standard dotted DNS name

Return Value:

    Ptr to domain name of pszName.
    NULL if pszName is in root domain.

--*/
{
    PSTR    pdomain = (PSTR) pszName;
    PSTR    ptld = NULL;

    //
    //  find last domain name in name
    //

    while ( pdomain = Dns_GetDomainName(pdomain) )
    {
        ptld = (PSTR) pdomain;
    }
    return  ptld;
}



BOOL
_fastcall
Dns_IsNameShort(
    IN      PCSTR           pszName
    )
/*++

Routine Description:

    Determine if a name is a multi-label DNS name.

    This is a test of whether name at least one non-terminal dot.

Arguments:

    pszName - standard dotted DNS name

Return Value:

    TRUE if multiple labels.
    FALSE otherwise.

--*/
{
    INT     nameLen;

    //  trailing domain? -- done

    if ( Dns_GetDomainName( pszName ) )
    {
        return  FALSE;
    }

    //  otherwise test for valid label

    nameLen = strlen( pszName );
    if ( nameLen <= DNS_MAX_LABEL_LENGTH )
    {
        return  TRUE;
    }
    nameLen--;
    if ( nameLen == DNS_MAX_LABEL_LENGTH &&
         pszName[nameLen] == '.')
    {
        return  TRUE; 
    }
    return  FALSE;
}

    
BOOL
_fastcall
Dns_IsNameFQDN(
    IN      PCSTR           pszName
    )
/*++

Routine Description:

    Determine if a name is a fully qualified DNS name (FQDN).

    This is a test of whether name has trailing dot.

Arguments:

    pszName - standard dotted DNS name

Return Value:

    TRUE if FQDN.
    FALSE otherwise.

--*/
{
    DWORD nameLen = strlen( pszName );

    if ( nameLen == 0 )
    {
        return FALSE;
    }

    if ( pszName[nameLen - 1] == '.' )
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}


DWORD
_fastcall
Dns_GetNameAttributes(
    IN      PCSTR           pszName
    )
/*++

Routine Description:

    Determine the attributes that a name has.

    Note, this assumes name already in UTF-8

Arguments:

    pszName - standard dotted DNS name

Return Value:

    DWORD with possible flags:

    DNS_NAME_IS_FQDN
    DNS_NAME_SINGLE_LABEL
    DNS_NAME_MULTI_LABEL

--*/
{
    DWORD dwAttributes = DNS_NAME_UNKNOWN;

    if ( Dns_IsNameFQDN( pszName ) )
    {
        dwAttributes = DNS_NAME_IS_FQDN;
    }

    if ( Dns_IsNameShort( pszName ) )
    {
        dwAttributes |= DNS_NAME_SINGLE_LABEL;
    }
    else
    {
        dwAttributes |= DNS_NAME_MULTI_LABEL;
    }

    return dwAttributes;
}



DNS_STATUS
Dns_ValidateAndCategorizeDnsNameEx(
    IN      PCHAR           pchName,
    IN      DWORD           cchNameLength,
    OUT     PDWORD          pLabelCount
    )
/*++

Routine Description:

    Determine type of name.

    Three types:
        1) FQDN -- dot on end, signifies full DNS name, never appended

        2) dotted -- dot in name;  probably FQDN, but may need to be appended
            (as in file store)

        3) single part -- single part name (not FQDN), always appended with zone
            or default domain name

Arguments:

    pchName         -- ptr to name

    cchNameLength   -- name length

    pLabelCount     -- address to receive label count

Return Value:

    DNS_STATUS_FQDN
    DNS_STATUS_DOTTED_NAME
    DNS_STATUS_SINGLE_PART_NAME
    DNS_ERROR_INVALID_NAME on non-DNS name

--*/
{
    register PCHAR  pch;
    register CHAR   ch;
    PCHAR           pchstop;
    BOOL            fdotted = FALSE;
    DWORD           labelCount = 0;
    DWORD           charCount = 0;
    DNS_STATUS      status = DNS_STATUS_SINGLE_PART_NAME;

    //
    //  name length for string
    //

    if ( cchNameLength == 0 )
    {
        cchNameLength = strlen( pchName );
    }
    if ( cchNameLength > DNS_MAX_NAME_LENGTH ||
         cchNameLength == 0 )
    {
        goto InvalidName;
    }

    //
    //  run through name
    //

    pch = pchName;
    pchstop = pch + cchNameLength;

    while ( pch < pchstop )
    {
        ch = *pch++;
        if ( ch == '.' )
        {
            if ( charCount > DNS_MAX_LABEL_LENGTH )
            {
                goto InvalidName;
            }
            if ( charCount > 0 )
            {
                labelCount++;
                charCount = 0;
                status = DNS_STATUS_DOTTED_NAME;
                continue;
            }
            else
            {
                //  only valid zero label name is "."
                if ( pch == pchstop &&
                     pch-1 == pchName )
                {
                    break;
                }
                goto InvalidName;
            }
        }
        else if ( ch == 0 )
        {
            DNS_ASSERT( FALSE );
            break;
        }

        //  regular character
        charCount++;
    }

    //
    //  handle last label
    //      - if count, then boost label count
    //      - if zero and previously had dot, then string
    //          ended in dot and is FQDN
    //

    if ( charCount > 0 )
    {
        if ( charCount > DNS_MAX_LABEL_LENGTH )
        {
            goto InvalidName;
        }
        labelCount++;
    }
    else if ( status == DNS_STATUS_DOTTED_NAME )
    {
        status = DNS_STATUS_FQDN;
    }

    //  return label count

    if ( pLabelCount )
    {
        *pLabelCount = labelCount;
    }

    DNSDBG( TRACE, (
        "Leave Dns_ValidateAndCategorizeNameEx()\n"
        "\tstatus       = %d\n"
        "\tlabel count  = %d\n",
        status,
        labelCount ));

    return( status );


InvalidName:

    if ( pLabelCount )
    {
        *pLabelCount = 0;
    }

    DNSDBG( TRACE, (
        "Leave Dns_ValidateAndCategorizeNameEx()\n"
        "\tstatus = ERROR_INVALID_NAME\n" ));

    return( DNS_ERROR_INVALID_NAME );
}



DWORD
Dns_NameLabelCount(
    IN      PCSTR           pszName
    )
/*++

Routine Description:

    Return name label count.

Arguments:

    pszName -- ptr to name

Return Value:

    Label count if valid name.
    Zero on root name or error.

--*/
{
    DWORD       labelCount = 0;
    DNS_STATUS  status;

    //
    //  call real routine
    //

    status = Dns_ValidateAndCategorizeDnsNameEx(
                    (PCHAR) pszName,
                    0,
                    & labelCount );

    if ( status == DNS_ERROR_INVALID_NAME )
    {
        labelCount = 0;
    }

    return( labelCount );
}



PSTR
Dns_NameAppend_A(
    OUT     PCHAR           pNameBuffer,
    IN      DWORD           BufferLength,
    IN      PSTR            pszName,
    IN      PSTR            pszDomain
    )
/*++

Routine Description:

    Write appended name to buffer (ANSI or UTF8).

Arguments:

    pNameBuffer -- name buffer to write to

    BufferLength -- buffer length

    pszName -- name to append domain to

    pszDomain -- domain name

Return Value:

    Ptr to buffer with appended domain name.
    NULL on invalid (too long) name.

--*/
{
    DWORD   length1;
    DWORD   length2;
    DWORD   totalLength;

    DNSDBG( TRACE, ( "Dns_NameAppend_A()\n" ));

    //
    //  appending NULL domain?
    //

    if ( !pszDomain )
    {
        totalLength = strlen( pszName );
        if ( totalLength >= BufferLength )
        {
            return( NULL );
        }
        RtlCopyMemory(
            pNameBuffer,
            pszName,
            totalLength );

        pNameBuffer[ totalLength ] = 0;
    
        return( pNameBuffer );
    }

    //
    //  get name lengths -- make sure we fit length
    //

    length1 = strlen( pszName );
    if ( pszName[ length1-1] == '.' )
    {
        length1--;
    }

    length2 = strlen( pszDomain );
    
    totalLength = length1 + length2 + 1;
    if ( totalLength >= BufferLength )
    {
        return( NULL );
    }

    //
    //  copy to buffer
    //

    RtlCopyMemory(
        pNameBuffer,
        pszName,
        length1 );

    pNameBuffer[ length1 ] = '.';

    RtlCopyMemory(
        & pNameBuffer[length1+1],
        pszDomain,
        length2 );

    pNameBuffer[ totalLength ] = 0;

    return( pNameBuffer );
}



PWSTR
Dns_NameAppend_W(
    OUT     PWCHAR          pNameBuffer,
    IN      DWORD           BufferLength,
    IN      PWSTR           pwsName,
    IN      PWSTR           pwsDomain
    )
/*++

Routine Description:

    Write appended name to buffer (unicode).

Arguments:

    pNameBuffer -- name buffer to write to

    BufferLength -- buffer length in WCHAR

    pwsName -- name to append domain to

    pwsDomain -- domain name

Return Value:

    Ptr to buffer with appended domain name.
    NULL on invalid (too long) name.

--*/
{
    DWORD   length1;
    DWORD   length2;
    DWORD   totalLength;

    DNSDBG( TRACE, ( "Dns_NameAppend_W()\n" ));

    //
    //  appending NULL domain?
    //

    if ( !pwsDomain )
    {
        totalLength = wcslen( pwsName );
        if ( totalLength >= BufferLength )
        {
            return( NULL );
        }
        RtlCopyMemory(
            pNameBuffer,
            pwsName,
            totalLength*sizeof(WCHAR) );

        pNameBuffer[ totalLength ] = 0;
    
        return( pNameBuffer );
    }

    //
    //  get name lengths -- make sure we fit length
    //

    length1 = wcslen( pwsName );
    if ( pwsName[ length1-1] == '.' )
    {
        length1--;
    }

    length2 = wcslen( pwsDomain );
    
    totalLength = length1 + length2 + 1;
    if ( totalLength >= BufferLength )
    {
        return( NULL );
    }

    //
    //  copy to buffer
    //

    RtlCopyMemory(
        pNameBuffer,
        pwsName,
        length1*sizeof(WCHAR) );

    pNameBuffer[ length1 ] = '.';

    RtlCopyMemory(
        & pNameBuffer[length1+1],
        pwsDomain,
        length2*sizeof(WCHAR) );

    pNameBuffer[ totalLength ] = 0;

    return( pNameBuffer );
}



PSTR
Dns_SplitHostFromDomainName_A(
    IN      PSTR            pszName
    )
/*++

Routine Description:

    Split host name from domain name.

    Combines getting domain name and splitting
    off host name.

Arguments:

    pszName - standard dotted DNS name

Return Value:

    Ptr to domain name of pszName.
    NULL if pszName is in root domain.

--*/
{
    PSTR    pnameDomain;

    //
    //  get domain name
    //  if exists, NULL terminate host name part
    //

    pnameDomain = Dns_GetDomainName( (PCSTR)pszName );
    if ( pnameDomain )
    {
        if ( pnameDomain <= pszName )
        {
            return  NULL;
        }
        *(pnameDomain-1) = 0;
    }

    return  pnameDomain;
}



BOOL
_fastcall
Dns_IsNameNumeric_A(
    IN      PCSTR           pszName
    )
/*++

Routine Description:

    Determine if numeric name.

    Note, this assumes name already in UTF-8

Arguments:

    pszName - standard dotted DNS name

Return Value:

    TRUE if all numeric.
    FALSE otherwise.

--*/
{
    CHAR    ch;
    BOOL    fnumeric = FALSE;

    //
    //  check if everything in name is numeric
    //      - dotted names can be numeric
    //      - "." not numeric
    //

    while( ch = *pszName++ )
    {
        if ( ch >= '0' && ch <= '9' )
        {
            fnumeric = TRUE;
            continue;
        }
        else if ( ch == '.' )
        {
            continue;
        }
        return  FALSE;
    }

    return  fnumeric;
}



//
//  Wrappers for most common name conversions
//

DWORD
Dns_NameCopyWireToUnicode(
    OUT     PWCHAR          pBufferUnicode,
    IN      PCSTR           pszNameWire
    )
/*++

Routine Description:

    Convert name from wire to unicode.

    Simple wrapper on Dns_NameCopy for common operation:
        - unicode to wire
        - NULL terminated name
        - standard length buffer

Arguments:

    pBufferUnicode -- unicode result buffer

    pszNameWire - name in wire format

Return Value:

    Count of bytes copied if successful.
    Zero on error -- name too long or conversion error.

--*/
{
    DWORD   bufferLength = DNS_MAX_NAME_BUFFER_LENGTH_UNICODE;

    //
    //  copy name back to unicode
    //

    return Dns_NameCopy(
                (PCHAR) pBufferUnicode,
                & bufferLength,
                (PCHAR) pszNameWire,
                0,                      // null terminated
                DnsCharSetWire,
                DnsCharSetUnicode );
}



DWORD
Dns_NameCopyUnicodeToWire(
    OUT     PCHAR           pBufferWire,
    IN      PCWSTR          pwsNameUnicode
    )
/*++

Routine Description:

    Convert name from unicode to wire.

    Simple wrapper on Dns_NameCopy for common operation:
        - unicode to wire
        - NULL terminated name
        - standard length buffer

Arguments:

    pBufferWire -- wire format result buffer

    pwsNameUnicode - name in unicode

Return Value:


    Count of bytes copied if successful.
    Zero on error -- name too long or conversion error.

--*/
{
    DWORD   bufferLength = DNS_MAX_NAME_BUFFER_LENGTH;

    //
    //  copy name to wire format
    //

    return Dns_NameCopy(
                pBufferWire,
                & bufferLength,
                (PCHAR) pwsNameUnicode,
                0,                      // null terminated
                DnsCharSetUnicode,
                DnsCharSetWire );
}



DWORD
Dns_NameCopyStandard_W(
    OUT     PWCHAR          pBuffer,
    IN      PCWSTR          pwsNameUnicode
    )
/*++

Routine Description:

    Copy unicode name.

    Simple wrapper on Dns_NameCopy for common operation:
        - unicode to unicode
        - NULL terminated name
        - standard length buffer

Arguments:

    pBuffer -- wire format result buffer

    pwsNameUnicode - name in unicode

Return Value:

    Count of bytes copied if successful.
    Zero on error -- name too long or conversion error.

--*/
{
    DWORD   bufferLength = DNS_MAX_NAME_BUFFER_LENGTH_UNICODE;

    //
    //  copy name
    //

    return Dns_NameCopy(
                (PCHAR) pBuffer,
                & bufferLength,
                (PCHAR) pwsNameUnicode,
                0,                      // null terminated
                DnsCharSetUnicode,
                DnsCharSetUnicode );
}



DWORD
Dns_NameCopyStandard_A(
    OUT     PCHAR           pBuffer,
    IN      PCSTR           pszName
    )
/*++

Routine Description:

    Convert name from unicode to wire.

    Simple wrapper on Dns_NameCopy for common operation:
        - unicode to wire
        - NULL terminated name
        - standard length buffer

Arguments:

    pBuffer -- wire format result buffer

    pszName - name in narrow char set

Return Value:

    Count of bytes copied if successful.
    Zero on error -- name too long or conversion error.

--*/
{
    DWORD   bufferLength = DNS_MAX_NAME_BUFFER_LENGTH;

    //
    //  copy name
    //

    return Dns_NameCopy(
                pBuffer,
                & bufferLength,
                (PCHAR) pszName,
                0,                      // null terminated
                DnsCharSetUtf8,
                DnsCharSetUtf8 );
}


//
//  End name.c
//