/*++ Copyright (c) 1997 Microsoft Corporation Module Name: sam.c Abstract: Domain Name System (DNS) Server -- Admin Client API Functions developed for SAM as simplified interfaces \ examples. Author: Jim Gilroy (jamesg) September 1997 Environment: User Mode - Win32 Revision History: --*/ #include "dnsclip.h" #define MAX_SAM_BACKOFF (32000) // wait up to 32 seconds // // Type specific update functions. // VOID DNS_API_FUNCTION DnssrvFillRecordHeader( IN OUT PDNS_RPC_RECORD pRecord, IN DWORD dwTtl, IN DWORD dwTimeStamp, IN BOOL fSuppressNotify ) { pRecord->dwTtlSeconds = dwTtl; pRecord->dwTimeStamp = dwTimeStamp; pRecord->dwFlags = 0; if ( fSuppressNotify ) { pRecord->dwFlags |= DNS_RPC_FLAG_SUPPRESS_NOTIFY; } } DWORD DNS_API_FUNCTION DnssrvWriteNameToFlatBuffer( IN OUT PCHAR pchWrite, IN LPCSTR pszName ) /*++ Routine Description: Write DNS name (or string) to flat buffer. Arguments: pchWrite -- location to write name at pszName -- name or string to write Return Value: Length of name written, including count byte. Caller may countinue in buffer at pchWrite + returned length. 0 on name error. --*/ { DWORD length; // // get name length // whether name or string, must be 255 or less to fit // counted character format length = strlen( pszName ); if ( length > DNS_MAX_NAME_LENGTH ) { return( 0 ); } // // write name to desired location // - count byte first // - then name itself * (PUCHAR) pchWrite = (UCHAR) length; pchWrite++; RtlCopyMemory( pchWrite, pszName, length ); return( length+1 ); } DNS_STATUS DNS_API_FUNCTION DnssrvFillOutSingleIndirectionRecord( IN OUT PDNS_RPC_RECORD pRecord, IN WORD wType, IN LPCSTR pszName ) { PCHAR pwriteName; DWORD length; DWORD dataLength = 0; // // find name write position and final data length for various types // switch( wType ) { case DNS_TYPE_MX: pwriteName = (PCHAR) &pRecord->Data.MX.nameExchange; dataLength += sizeof(WORD); break; case DNS_TYPE_SRV: pwriteName = (PCHAR) &pRecord->Data.SRV.nameTarget; dataLength += 3*sizeof(WORD); break; default: // all plain single-indirection types (CNAME, NS, PTR, etc) pwriteName = (PCHAR) &pRecord->Data.PTR.nameNode; } // // write name // - note name's datalength includes count character // length = DnssrvWriteNameToFlatBuffer( pwriteName, pszName ); if ( !length ) { return( ERROR_INVALID_DATA ); } dataLength += length; // set record header fields pRecord->wType = wType; pRecord->wDataLength = (WORD)dataLength; ASSERT( (PCHAR)pRecord + SIZEOF_DNS_RPC_RECORD_HEADER + dataLength == pwriteName + length ); return( ERROR_SUCCESS ); } DNS_STATUS DNS_API_FUNCTION DnssrvAddARecord( IN LPCWSTR pwszServer, IN LPCSTR pszNodeName, IN IP_ADDRESS ipAddress, IN DWORD dwTtl, IN DWORD dwTimeout, IN BOOL fSuppressNotify ) { DNS_RPC_RECORD record; // pack up data and send DnssrvFillRecordHeader( & record, dwTtl, dwTimeout, fSuppressNotify ); record.wType = DNS_TYPE_A; record.wDataLength = sizeof(IP_ADDRESS); record.Data.A.ipAddress = ipAddress; return DnssrvUpdateRecord( pwszServer, NULL, // zone not specified pszNodeName, &record, NULL ); } DNS_STATUS DNS_API_FUNCTION DnssrvAddCnameRecord( IN LPCWSTR pwszServer, IN LPCSTR pszNodeName, IN LPCSTR pszCannonicalName, IN DWORD dwTtl, IN DWORD dwTimeout, IN BOOL fSuppressNotify ) { DNS_RPC_RECORD record; // pack up data and send DnssrvFillRecordHeader( & record, dwTtl, dwTimeout, fSuppressNotify ); DnssrvFillOutSingleIndirectionRecord( & record, DNS_TYPE_CNAME, pszCannonicalName ); return DnssrvUpdateRecord( pwszServer, NULL, // zone not specified pszNodeName, &record, NULL ); } DNS_STATUS DNS_API_FUNCTION DnssrvAddMxRecord( IN LPCWSTR pwszServer, IN LPCSTR pszNodeName, IN LPCSTR pszMailExchangeHost, IN WORD wPreference, IN DWORD dwTtl, IN DWORD dwTimeout, IN BOOL fSuppressNotify ) { DNS_RPC_RECORD record; // pack up data and send DnssrvFillRecordHeader( & record, dwTtl, dwTimeout, fSuppressNotify ); DnssrvFillOutSingleIndirectionRecord( & record, DNS_TYPE_MX, pszMailExchangeHost ); record.Data.MX.wPreference = wPreference; return DnssrvUpdateRecord( pwszServer, NULL, // zone not specified pszNodeName, &record, NULL ); } DNS_STATUS DNS_API_FUNCTION DnssrvAddNsRecord( IN LPCWSTR pwszServer, IN LPCSTR pszNodeName, IN LPCSTR pszNsHostName, IN DWORD dwTtl, IN DWORD dwTimeout, IN BOOL fSuppressNotify ) { DNS_RPC_RECORD record; DWORD length; // pack up data and send DnssrvFillRecordHeader( & record, dwTtl, dwTimeout, fSuppressNotify ); DnssrvFillOutSingleIndirectionRecord( & record, DNS_TYPE_NS, pszNsHostName ); return DnssrvUpdateRecord( pwszServer, NULL, // zone not specified pszNodeName, &record, NULL ); } DNS_STATUS DNS_API_FUNCTION DnssrvConcatDnsNames( OUT PCHAR pszResult, IN LPCSTR pszDomain, IN LPCSTR pszName ) /*++ Routine Description: Concatenate two DNS names. Result is FQDN DNS name -- dot terminated. Note, currently no validity check is done on names being appended. If they are invalid DNS names then result is invalid DNS name. Arguments: pszResult -- result name buffer; should be DNS_MAX_NAME_BUFFER_LEN to be protected from name overwrite pszDomain -- domain name to write pszName -- name (like a host name) to prepend to domain name Return Value: ERROR_SUCCESS if successful. pszResult then contains FQDN DNS_ERROR_INVALID_NAME on failure. --*/ { DWORD lengthDomain; DWORD lengthName; // handle NULL name case if ( !pszName ) { strcpy( pszResult, pszDomain ); return( ERROR_SUCCESS ); } // // build combined name // - verify combined length within DNS limit // - put dot between names // - dot terminate combined name (make FQDN) // lengthDomain = strlen( pszDomain ); lengthName = strlen( pszName ); if ( lengthDomain + lengthName + 2 > DNS_MAX_NAME_LENGTH ) { return( DNS_ERROR_INVALID_NAME ); } strcpy( pszResult, pszName ); if ( pszDomain[lengthName-1] != '.' ) { strcat( pszResult, "." ); } strcat( pszResult, pszDomain ); if ( pszDomain[lengthDomain-1] != '.' ) { strcat( pszResult, "." ); } return( ERROR_SUCCESS ); } DNS_STATUS DNS_API_FUNCTION DnssrvSbsAddClientToIspZone( IN LPCWSTR pwszServer, IN LPCSTR pszIspZone, IN LPCSTR pszClient, IN LPCSTR pszClientHost, IN IP_ADDRESS ipClientHost, IN DWORD dwTtl ) { DNS_STATUS status; INT recordCount = (-1); INT backoff = 0; CHAR szdomain[ DNS_MAX_NAME_BUFFER_LENGTH ]; CHAR szhost[ DNS_MAX_NAME_BUFFER_LENGTH ]; CHAR sztarget[ DNS_MAX_NAME_BUFFER_LENGTH ]; // // to register in ISP zone need to register // - MX // - CNAME for web server // - host for web and mail server // // build FQDN for domain and host and cname // - do this now so we know names are valid // status = DnssrvConcatDnsNames( szdomain, pszIspZone, pszClient ); if ( status != ERROR_SUCCESS ) { return( status ); } status = DnssrvConcatDnsNames( szhost, szdomain, pszClientHost ); if ( status != ERROR_SUCCESS ) { return( status ); } status = DnssrvConcatDnsNames( sztarget, szdomain, "www" ); if ( status != ERROR_SUCCESS ) { return( status ); } // // do registrations, looping in case server is unable to complete // immediately but is open for update // while ( 1 ) { // if retrying backoff, but continue trying if ( backoff ) { if ( backoff > MAX_SAM_BACKOFF ) { break; } Sleep( backoff ); } backoff += 1000; // // remove any old entries at client domain // if ( recordCount < 0 ) { status = DnssrvDeleteNode( pwszServer, pszIspZone, szdomain, 1 // delete subtree ); if ( status == DNS_ERROR_NAME_DOES_NOT_EXIST ) { status = ERROR_SUCCESS; } } // register A record else if ( recordCount < 1 ) { status = DnssrvAddARecord( pwszServer, szhost, ipClientHost, dwTtl, 0, // no timeout TRUE ); // suppress notify } // register CNAME for WEB server else if ( recordCount < 2 ) { status = DnssrvAddCnameRecord( pwszServer, sztarget, szhost, dwTtl, 0, // no timeout TRUE ); // suppress notify } // register MX at client's domain root // then at wildcard else if ( recordCount < 3 ) { status = DnssrvAddMxRecord( pwszServer, szdomain, szhost, 10, // preference dwTtl, 0, // no timeout TRUE ); // suppress notify } else if ( recordCount < 4 ) { // prepare *..isp name for wildcard MX record status = DnssrvConcatDnsNames( sztarget, szdomain, "*" ); if ( status != ERROR_SUCCESS ) { ASSERT( FALSE ); break; } status = DnssrvAddMxRecord( pwszServer, sztarget, szhost, 10, // preference dwTtl, 0, // no timeout TRUE ); // suppress notify } // all desired records registered else { ASSERT( recordCount == 4 ); break; } // // check status on operations // - if successful, inc count and reset backoff to move // onto next operation // - if zone locked, continue after backoff // - other errors are terminal // if ( status == ERROR_SUCCESS || status == DNS_ERROR_RECORD_ALREADY_EXISTS ) { recordCount++; backoff = 0; } else if ( status == DNS_ERROR_ZONE_LOCKED ) { continue; } else { break; } } return( status ); } // // Record deleting functions. // // This example uses A records. // Could be cloned to handle MX or CNAME or NS. // OR could expand this function to choose type // BOOL DNS_API_FUNCTION DnssrvMatchDnsRpcName( IN PDNS_RPC_NAME pRpcName, IN LPCSTR pszName ) { CHAR nameBuf[ DNS_MAX_NAME_BUFFER_LENGTH ] = ""; RtlCopyMemory( nameBuf, pRpcName->achName, pRpcName->cchNameLength ); return Dns_NameCompare_UTF8( nameBuf, (LPSTR)pszName ); } DNS_STATUS DNS_API_FUNCTION DnssrvDeleteARecord( IN LPCWSTR pwszServer, IN LPCSTR pszNodeName, IN IP_ADDRESS ipAddress, IN BOOL fSuppressNotify ) { DNS_RPC_RECORD record; DNSDBG( RPC2, ( "DnssrvDeleteARecord()\n" )); // pack up data and send DnssrvFillRecordHeader( & record, 0, // TTL irrelevant for delete 0, // timeout irrelevant fSuppressNotify ); record.wType = DNS_TYPE_A; record.wDataLength = sizeof(IP_ADDRESS); record.Data.A.ipAddress = ipAddress; return DnssrvUpdateRecord( pwszServer, NULL, // zone not specified pszNodeName, NULL, // no add &record ); } DNS_STATUS DNS_API_FUNCTION DnssrvDeleteCnameRecord( IN LPCWSTR pwszServer, IN LPCSTR pszNodeName, IN LPCSTR pszCannonicalName, IN BOOL fSuppressNotify ) { DNS_RPC_RECORD record; // pack up data and send DnssrvFillRecordHeader( & record, 0, // TTL irrelevant for delete 0, // timeout irrelevant fSuppressNotify ); DnssrvFillOutSingleIndirectionRecord( & record, DNS_TYPE_CNAME, pszCannonicalName ); return DnssrvUpdateRecord( pwszServer, NULL, // zone not specified pszNodeName, NULL, &record ); } DNS_STATUS DNS_API_FUNCTION DnssrvDeleteMxRecord( IN LPCWSTR pwszServer, IN LPCSTR pszNodeName, IN LPCSTR pszMailExchangeHost, IN WORD wPreference, IN BOOL fSuppressNotify ) { DNS_RPC_RECORD record; // pack up data and send DnssrvFillRecordHeader( & record, 0, // TTL irrelevant for delete 0, // timeout irrelevant fSuppressNotify ); DnssrvFillOutSingleIndirectionRecord( & record, DNS_TYPE_MX, pszMailExchangeHost ); record.Data.MX.wPreference = wPreference; return DnssrvUpdateRecord( pwszServer, NULL, // zone not specified pszNodeName, NULL, &record ); } DNS_STATUS DNS_API_FUNCTION DnssrvDeleteNsRecord( IN LPCWSTR pwszServer, IN LPCSTR pszNodeName, IN LPCSTR pszNsHostName, IN BOOL fSuppressNotify ) { DNS_RPC_RECORD record; // pack up data and send DnssrvFillRecordHeader( & record, 0, // TTL irrelevant for delete 0, // timeout irrelevant fSuppressNotify ); DnssrvFillOutSingleIndirectionRecord( & record, DNS_TYPE_NS, pszNsHostName ); return DnssrvUpdateRecord( pwszServer, NULL, // zone not specified pszNodeName, NULL, &record ); } #if 0 DNS_STATUS DNS_API_FUNCTION DnssrvSbsDeleteRecord( IN LPCWSTR pwszServer, IN LPCSTR pszZone, IN LPCSTR pszDomain, IN LPCSTR pszOwner, IN WORD wType, IN LPCSTR pszDataName, IN IP_ADDRESS ipHost ) { PDNS_RPC_RECORD prpcRecord; DNS_STATUS status; BOOL ffound; INT countRecords; PBYTE pbyte; PBYTE pstopByte; PBYTE pbuffer; DWORD bufferLength; CHAR szdomain[ DNS_MAX_NAME_BUFFER_LENGTH ]; CHAR szhost[ DNS_MAX_NAME_BUFFER_LENGTH ]; DNSDBG( RPC2, ( "DnssrvSbsDeleteRecord()\n" )); // // to register in ISP zone need to register // - MX // - CNAME for web server // - host for web and mail server // // build FQDN for domain and host and cname // - do this now so we know names are valid // status = DnssrvConcatDnsNames( szdomain, pszZone, pszDomain ); if ( status != ERROR_SUCCESS ) { return( status ); } status = DnssrvConcatDnsNames( szhost, szdomain, pszOwner ); if ( status != ERROR_SUCCESS ) { return( status ); } // // enumerate records at a particular node // status = DnssrvEnumRecords( pwszServer, szhost, NULL, wType, ( DNS_RPC_VIEW_ALL_DATA | DNS_RPC_VIEW_NO_CHILDREN ), & bufferLength, & pbuffer ); if ( status != ERROR_SUCCESS ) { DNSDBG( RPC2, ( "DnssrvEnumRecord() failed %p.\n", status )); return( status ); } pstopByte = pbuffer + bufferLength; pbyte = pbuffer; // // read node info // - extract record count // countRecords = ((PDNS_RPC_NODE)pbyte)->wRecordCount; pbyte += ((PDNS_RPC_NODE)pbyte)->wLength; pbyte = DNS_NEXT_DWORD_PTR(pbyte); // // loop through all records in node, delete appropriate one // DNSDBG( RPC2, ( "Checking %d records for matching record.\n", countRecords )); while ( countRecords-- ) { prpcRecord = (PDNS_RPC_RECORD) pbyte; if ( !DNS_IS_RPC_RECORD_WITHIN_BUFFER( prpcRecord, pstopByte ) ) { DNS_PRINT(( "ERROR: Bogus buffer at %p\n" "\tRecord leads past buffer end at %p\n" "\twith %d records remaining.\n", prpcRecord, pstopByte, countRecords+1 )); DNS_ASSERT( FALSE ); return( DNS_ERROR_INVALID_DATA ); } // if type not desired type, then not interesting if ( prpcRecord->wType != wType ) { DNS_ASSERT( FALSE ); return( DNS_ERROR_INVALID_DATA ); } DNSDBG( RPC2, ( "Checking record at %p for matching data of type %d.\n", prpcRecord, wType )); // // check for data match, delete if match // switch ( wType ) { case DNS_TYPE_A: ffound = ( prpcRecord->Data.A.ipAddress == ipHost ); DNSDBG( RPC2, ( "%s match between A record %lx and desired IP %lx\n", ffound ? "Found" : "No", prpcRecord->Data.A.ipAddress, ipHost )); break; case DNS_TYPE_MX: ffound = DnssrvMatchDnsRpcName( & prpcRecord->Data.MX.nameExchange, pszDataName ); break; case DNS_TYPE_NS: case DNS_TYPE_CNAME: case DNS_TYPE_PTR: ffound = DnssrvMatchDnsRpcName( & prpcRecord->Data.MX.nameExchange, pszDataName ); break; default: return( DNS_ERROR_INVALID_DATA ); } if ( ffound ) { DNSDBG( RPC2, ( "Found record (handle = %p) with desired data\n" "\t... deleting record\n", prpcRecord->hRecord )); status = DnssrvDeleteRecord( pwszServer, szhost, prpcRecord->hRecord ); if ( status != ERROR_SUCCESS ) { return( status ); } // shouldn't need to continue, as no duplicates allowed in general case // however to rule out glue or WINS cached data, continue compare\delete // until node is clear } // position ourselves at next record pbyte = (PCHAR) DNS_GET_NEXT_RPC_RECORD( prpcRecord ); // continue looking for matching records } return( ERROR_SUCCESS ); } #endif DNS_STATUS DNS_API_FUNCTION DnssrvSbsDeleteRecord( IN LPCWSTR pwszServer, IN LPCSTR pszZone, IN LPCSTR pszDomain, IN LPCSTR pszOwner, IN WORD wType, IN LPCSTR pszDataName, IN IP_ADDRESS ipHost ) { DNS_STATUS status; CHAR szdomain[ DNS_MAX_NAME_BUFFER_LENGTH ]; CHAR szhost[ DNS_MAX_NAME_BUFFER_LENGTH ]; DNSDBG( RPC2, ( "DnssrvSbsDeleteRecord()\n" )); // // to register in ISP zone need to register // - MX // - CNAME for web server // - host for web and mail server // // build FQDN for domain and host and cname // - do this now so we know names are valid // status = DnssrvConcatDnsNames( szdomain, pszZone, pszDomain ); if ( status != ERROR_SUCCESS ) { return( status ); } status = DnssrvConcatDnsNames( szhost, szdomain, pszOwner ); if ( status != ERROR_SUCCESS ) { return( status ); } // // dispatch to appropriate type delete routine // switch ( wType ) { case DNS_TYPE_A: return DnssrvDeleteARecord( pwszServer, szhost, ipHost, FALSE // no notify suppress ); case DNS_TYPE_NS: return DnssrvDeleteNsRecord( pwszServer, szhost, pszDataName, FALSE // no notify suppress ); case DNS_TYPE_CNAME: return DnssrvDeleteCnameRecord( pwszServer, szhost, pszDataName, FALSE // no notify suppress ); case DNS_TYPE_MX: return DnssrvDeleteMxRecord( pwszServer, szhost, pszDataName, (WORD) ipHost, FALSE // no notify suppress ); default: return( DNS_ERROR_INVALID_DATA ); } return( ERROR_SUCCESS ); } // // End sam.c //