/*++ 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 ]; 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 ); 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; } DNSDBG( READ, ( "Unicode converted string %.*S\n" "\tlength = %d\n" "\tlast error = %d\n", length, unicodeString, length, GetLastError() )); // 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 // 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_GetDomainNameA( 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_GetDomainNameW( 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; } PSTR _fastcall Dns_GetTldForNameA( IN PCSTR pszName ) /*++ Routine Description: Get domain name of DNS name. 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_GetDomainNameA(pdomain) ) { ptld = (PSTR) pdomain; } return ptld; } PWSTR _fastcall Dns_GetTldForNameW( IN PCWSTR pszName ) { PWSTR pdomain = (PWSTR) pszName; PWSTR ptld = NULL; // // find last domain name in name // while ( pdomain = Dns_GetDomainNameW(pdomain) ) { ptld = pdomain; } return ptld; } BOOL _fastcall Dns_IsNameShortA( 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_GetDomainNameA( 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_IsNameShortW( IN PCWSTR pszName ) { INT nameLen; // trailing domain? -- done if ( Dns_GetDomainNameW( pszName ) ) { return FALSE; } // otherwise test for valid label nameLen = wcslen( pszName ); if ( nameLen <= DNS_MAX_LABEL_LENGTH ) { return TRUE; } nameLen--; if ( nameLen == DNS_MAX_LABEL_LENGTH && pszName[nameLen] == L'.') { return TRUE; } return FALSE; } BOOL _fastcall Dns_IsNameNumericA( 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; } BOOL _fastcall Dns_IsNameNumericW( IN PCWSTR pszName ) { WCHAR 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; } BOOL _fastcall Dns_IsNameFQDN_A( 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; } } BOOL _fastcall Dns_IsNameFQDN_W( IN PCWSTR pszName ) { DWORD nameLen = wcslen( pszName ); if ( nameLen == 0 ) { return FALSE; } if ( pszName[nameLen - 1] == L'.' ) { return TRUE; } else { return FALSE; } } DWORD _fastcall Dns_GetNameAttributesA( 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 attributes = DNS_NAME_UNKNOWN; if ( Dns_IsNameFQDN_A( pszName ) ) { attributes = DNS_NAME_IS_FQDN; } if ( Dns_IsNameShortA( pszName ) ) { attributes |= DNS_NAME_SINGLE_LABEL; } else { attributes |= DNS_NAME_MULTI_LABEL; } return attributes; } DWORD _fastcall Dns_GetNameAttributesW( IN PCWSTR pszName ) { DWORD attributes = DNS_NAME_UNKNOWN; if ( Dns_IsNameFQDN_W( pszName ) ) { attributes = DNS_NAME_IS_FQDN; } if ( Dns_IsNameShortW( pszName ) ) { attributes |= DNS_NAME_SINGLE_LABEL; } else { attributes |= DNS_NAME_MULTI_LABEL; } return attributes; } 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 ); } DNS_STATUS Dns_ValidateAndCategorizeDnsNameA( IN PCSTR pszName, 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: pszName -- ptr to name 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 --*/ { // // call real function // return Dns_ValidateAndCategorizeDnsNameEx( (PCHAR) pszName, 0, // NULL terminated pLabelCount ); } DNS_STATUS Dns_ValidateAndCategorizeDnsNameW( IN PCWSTR pszName, 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: pszName -- ptr to name 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 PWCHAR pch; register WCHAR ch; PWCHAR pchstop; BOOL fdotted = FALSE; DWORD nameLength; DWORD labelCount = 0; DWORD charCount = 0; DNS_STATUS status = DNS_STATUS_SINGLE_PART_NAME; // // name length for string // nameLength = wcslen( pszName ); if ( nameLength > DNS_MAX_NAME_LENGTH || nameLength == 0 ) { goto InvalidName; } // // run through name // pch = (PWSTR) pszName; pchstop = pch + nameLength; while ( pch < pchstop ) { ch = *pch++; if ( ch == L'.' ) { 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 == pszName ) { 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_ValidateAndCategorizeNameW()\n" "\tstatus = %d\n" "\tlabel count = %d\n", status, labelCount )); return( status ); InvalidName: if ( pLabelCount ) { *pLabelCount = 0; } DNSDBG( TRACE, ( "Leave Dns_ValidateAndCategorizeNameW()\n" "\tstatus = ERROR_INVALID_NAME\n" )); return( DNS_ERROR_INVALID_NAME ); } DWORD Dns_NameLabelCountA( 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 ); } DWORD Dns_NameLabelCountW( IN PCWSTR 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_ValidateAndCategorizeDnsNameW( pszName, & 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( %A, %A )\n", pszName, pszDomain )); // // 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 ( !length1 ) { return NULL; } 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( %S, %S )\n", pwsName, pwsDomain )); // // 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 ( !length1 ) { return NULL; } if ( pwsName[ length1-1] == L'.' ) { length1--; } length2 = wcslen( pwsDomain ); totalLength = length1 + length2 + 1; if ( totalLength >= BufferLength ) { return( NULL ); } // // copy to buffer // RtlCopyMemory( pNameBuffer, pwsName, length1*sizeof(WCHAR) ); pNameBuffer[ length1 ] = L'.'; RtlCopyMemory( & pNameBuffer[length1+1], pwsDomain, length2*sizeof(WCHAR) ); pNameBuffer[ totalLength ] = 0; return( pNameBuffer ); } PSTR Dns_SplitHostFromDomainNameA( 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_GetDomainNameA( (PCSTR)pszName ); if ( pnameDomain ) { if ( pnameDomain <= pszName ) { return NULL; } *(pnameDomain-1) = 0; } return pnameDomain; } PWSTR Dns_SplitHostFromDomainNameW( IN PWSTR pszName ) { PWSTR pnameDomain; // // get domain name // if exists, NULL terminate host name part // pnameDomain = Dns_GetDomainNameW( (PCWSTR)pszName ); if ( pnameDomain ) { if ( pnameDomain <= pszName ) { return NULL; } *(pnameDomain-1) = 0; } return pnameDomain; } BOOL Dns_CopyNameLabelA( OUT PCHAR pBuffer, IN PCSTR pszName ) /*++ Routine Description: Copy first label in name. Arguments: pBuffer - buffer to hold label copy; must be DNS_MAX_LABEL_BUFFER_LENGTH pszName - standard dotted DNS name Return Value: TRUE on successful copy. FALSE on bad name. Copy buffer will still be valid. --*/ { CHAR ch; PCHAR plabel = pBuffer; PCHAR pstop; BOOL retval = TRUE; if ( !pszName ) { retval = FALSE; goto Done; } pstop = plabel + DNS_MAX_LABEL_LENGTH; // // find next "." in name, then return ptr to next character // while( ch = *pszName++ ) { if ( ch == '.' ) { if ( plabel == pBuffer ) { *plabel++ = ch; } break; } if ( plabel >= pstop ) { retval = FALSE; break; } *plabel++ = ch; } Done: *plabel = 0; return( retval ); } BOOL Dns_CopyNameLabelW( OUT PWCHAR pBuffer, IN PCWSTR pszName ) { WCHAR ch; PWCHAR plabel = pBuffer; PWCHAR pstop; BOOL retval = TRUE; if ( !pszName ) { retval = FALSE; goto Done; } pstop = plabel + DNS_MAX_LABEL_LENGTH; // // find next "." in name, then return ptr to next character // while( ch = *pszName++ ) { if ( ch == '.' ) { if ( plabel == pBuffer ) { *plabel++ = ch; } break; } if ( plabel >= pstop ) { retval = FALSE; break; } *plabel++ = ch; } Done: *plabel = 0; return( retval ); } // // 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 ); } // // Temp fix ups for functions exposed in dnslib.h // // DCR: delete when clean build with dnslib.h functions removed // PSTR _fastcall Dns_GetDomainName( IN PCSTR pszName ) { return Dns_GetDomainNameA( pszName ); } PWSTR _fastcall Dns_GetDomainName_W( IN PCWSTR pwsName ) { return Dns_GetDomainNameW( pwsName ); } PCHAR _fastcall Dns_GetTldForName( IN PCSTR pszName ) { return Dns_GetTldForNameA( pszName ); } BOOL _fastcall Dns_IsNameShort( IN PCSTR pszName ) { return Dns_IsNameShortA( pszName ); } BOOL _fastcall Dns_IsNameFQDN( IN PCSTR pszName ) { return Dns_IsNameFQDN_A( pszName ); } DWORD Dns_NameLabelCount( IN PCSTR pszName ) { return Dns_NameLabelCountA( pszName ); } DWORD _fastcall Dns_GetNameAttributes( IN PCSTR pszName ) { return Dns_GetNameAttributesA( pszName ); } // // End name.c //