/*++ Copyright (c) 1998 Microsoft Corporation Module Name : addr821.cxx Abstract: Set of functions to parse RFC 821 addresses. Author: Keith Lau (KeithLau) 2/17/98 Project: SMTP Server DLL Functions Exported: Revision History: --*/ /************************************************************ * Include Headers ************************************************************/ #include #include #include #define MAX_EMAIL_NAME 64 #define MAX_DOMAIN_NAME 250 #define MAX_INTERNET_NAME (MAX_EMAIL_NAME + MAX_DOMAIN_NAME + 2) // Quick and dirty string validation static BOOL pValidateStringPtr(LPSTR lpwszString, DWORD dwMaxLength) { if (IsBadStringPtr((LPCTSTR)lpwszString, dwMaxLength)) return(FALSE); while (dwMaxLength--) if (*lpwszString++ == 0) return(TRUE); return(FALSE); } // ======================================================================== // // Validation Parser stuff created by KeithLau on 2/17/98 // static char acOpen[] = "\"[<("; static char acClose[] = "\"]>)"; // // NOTE: RFC 821 and RFC 822 versions of this function are different!! // // This function finds braces pairs in a given string, and returns // pointers to the start and end of the first occurence of a // [nested] pair of braces. The starting and ending character // may be specified by the caller (starting and ending chars // must be unique). #define MAX_STATE_STACK_DEPTH 64 #define OPEN_DELIMITER 0x1 #define CLOSE_DELIMITER 0x2 #define OPEN_AND_CLOSE_DELIMITER (OPEN_DELIMITER | CLOSE_DELIMITER) typedef struct _BYTE_BUCKET { char cClosingDelimiter; // If this is an open delimiter, // this stores the correesponding closing // delimiter. Not used otherwise BYTE fFlags; // Flags, whether it is a delimiter } BYTE_BUCKET; static char *pFindNextUnquotedOccurrence(char *lpszString, DWORD dwStringLength, char cSearch, char *lpszOpenDelimiters, char *lpszCloseDelimiters, LPBOOL lpfNotFound) { char rgcState[MAX_STATE_STACK_DEPTH]; DWORD_PTR dwState = 0; DWORD dwDelimiters = 0; DWORD i; char ch; char *lpStart = lpszString; BOOL fFallThru; BYTE_BUCKET rgbBucket[128]; TraceFunctEnter("pFindNextUnquotedOccurrence"); if (cSearch > 127) return(NULL); *lpfNotFound = FALSE; dwDelimiters = lstrlen(lpszOpenDelimiters); if (dwDelimiters != (DWORD)lstrlen(lpszCloseDelimiters)) return(NULL); // Populate the bit bucket ZeroMemory(rgbBucket, 128 * sizeof(BYTE_BUCKET)); for (i = 0; i < dwDelimiters; i++) { rgbBucket[lpszOpenDelimiters[i]].cClosingDelimiter = lpszCloseDelimiters[i]; rgbBucket[lpszOpenDelimiters[i]].fFlags |= OPEN_DELIMITER; rgbBucket[lpszCloseDelimiters[i]].fFlags |= CLOSE_DELIMITER; } // dwState is the stack of unmatched open delimiters while (ch = *lpStart) { if (!dwStringLength) break; // Track the length dwStringLength--; // See if valid ASCII if (ch > 127) return(NULL); // If we are not in any quotes, and the char is found, // then we are done! if (!dwState && (ch == cSearch)) { DebugTrace((LPARAM)0, "Found %c at %p", ch, lpStart); return(lpStart); } // If it is a quoted char, we can skip it and the following // char right away ... If the char following a quote '\' is // the terminating NULL, we have an error. if (ch == '\\') { lpStart++; if (!*lpStart) return(NULL); dwStringLength--; } else { // Check the close case, too fFallThru = TRUE; // See if we have an opening quote of any sort if (rgbBucket[ch].fFlags & OPEN_DELIMITER) { // This is used to take care of the case when the // open and close delimiters are the same. If it is // an open delimiter, we do not check the close // case unless the close delimiter is the same. fFallThru = FALSE; // Special case for open = close if (dwState && rgcState[dwState-1] == ch && (rgbBucket[ch].fFlags & OPEN_AND_CLOSE_DELIMITER) == OPEN_AND_CLOSE_DELIMITER) { // Stack is not empty, top of stack contains the same // quote, and open quote == close, this is actually a // close quote in disguise. fFallThru = TRUE; } else { // Push the new open quote in the stack if (dwState == MAX_STATE_STACK_DEPTH) return(FALSE); DebugTrace((LPARAM)0, "Push[%u]: %c, looking for %c", dwState, ch, rgbBucket[ch].cClosingDelimiter); rgcState[dwState++] = rgbBucket[ch].cClosingDelimiter; } } // See if we have a closing quote of any sort if (fFallThru && (rgbBucket[ch].fFlags & CLOSE_DELIMITER)) { if (dwState) { // If we are closing the correct kind of quote, // pop the stack if (rgcState[dwState-1] == ch) { dwState--; DebugTrace((LPARAM)0, "Pop[%u] %c", dwState, ch); // Do a second check, in case we are looking // for a close quote if (!dwState && ch == cSearch) { DebugTrace((LPARAM)0, "Found %c at %p", ch, lpStart); return(lpStart); } } else { // Completely wrong closing brace. return(FALSE); } } else { // We are not in any quotes but we still see a // closing quote, so we have reached the end of our // current search scope! // Note that this is considered as not found // instead of an error *lpfNotFound = TRUE; return(NULL); } } } lpStart++; } *lpfNotFound = TRUE; TraceFunctLeave(); return(NULL); } static inline BOOL IsCrOrLf(char ch) { return(ch == '\r' || ch == '\n'); } static inline BOOL IsControl(char ch) { return( ((ch >= 0) && (ch <= 31)) || (ch == 127) ); } // // ::= "<" | ">" | "(" | ")" | "[" | "]" | "\" | "." // | "," | ";" | ":" | "@" """ | the control // characters (ASCII codes 0 through 31 inclusive and // 127) // static BOOL IsSpecial(char ch) { switch (ch) { case '(': case ')': case '<': case '>': case '@': case ',': case ':': case ';': case '\\': case '\"': case '.': case '[': case ']': return(TRUE); default: return(IsControl(ch)); } } static BOOL pIsSpecialOrSpace(char ch) { return((ch == ' ') || (ch == '\t') || (ch == '\0') || IsSpecial(ch)); } // // ::= any one of the 128 ASCII characters (no exceptions) // static inline BOOL pIsX(char ch) { return(TRUE); } // // ::= any one of the 52 alphabetic characters A through Z // in upper case and a through z in lower case // static inline BOOL pIsA(char ch) { return(((ch < 'A' || ch > 'z') || (ch > 'Z' && ch < 'a'))?FALSE:TRUE); } // // ::= any one of the ten digits 0 through 9 // static inline BOOL pIsD(char ch) { return((ch < '0' || ch > '9')?FALSE:TRUE); } // // ::= any one of the 128 ASCII characters, but not any // or // static inline BOOL pIsC(char ch) { return((ch == ' ' || IsSpecial(ch))?FALSE:TRUE); } // // ::= any one of the 128 ASCII characters except , // , quote ("), or backslash (\) // static inline BOOL pIsQ(char ch) { return((ch == '\"' || ch == '\\' || IsCrOrLf(ch))?FALSE:TRUE); } // // ::= | // static BOOL pValidateNumber(char *lpszStart, DWORD dwLength) { if (!dwLength) return(FALSE); while (dwLength--) { if (!pIsD(*lpszStart++)) return(FALSE); } return(TRUE); } // // ::= "." "." "." // ::= one, two, or three digits representing a decimal // integer value in the range 0 through 255 // static BOOL pValidateDotnum(char *lpszStart, DWORD dwLength) { char ch; DWORD dwSnums = 0; DWORD dwNumLength = 0; DWORD dwValue = 0; if (!dwLength || dwLength > 15) return(FALSE); while (dwLength--) { ch = *lpszStart++; if (pIsD(ch)) { // Do each digit and calculate running total dwValue *= 10; dwValue += (ch - '0'); dwNumLength++; } else if (ch == '.') { // There must be a number before each dot and // the running total must be between 0 and 255 if (!dwNumLength) return(FALSE); if (dwValue > 255) return(FALSE); // Reset the counter dwSnums++; dwValue = 0; dwNumLength = 0; } else return(FALSE); } // Do the last snum if (!dwNumLength) return(FALSE); if (dwValue > 255) return(FALSE); dwSnums++; // Each IP address must have 4 snums if (dwSnums != 4) return(FALSE); return(TRUE); } // // ::= """ """ // ::= "\" | "\" | | // static BOOL pValidateQuotedString(char *lpszStart, DWORD dwLength) { char ch; // At least 3 chars if (dwLength < 3) return(FALSE); // Must begin and end with double quotes if (lpszStart[0] != '\"' || lpszStart[dwLength-1] != '\"') return(FALSE); // Factor out the quotes dwLength -= 2; lpszStart++; // The inside must be while (dwLength--) { ch = *lpszStart++; // Each character must be either an escape pair or if (ch == '\\') { if (!dwLength) return(FALSE); dwLength--; lpszStart++; } else if (!pIsQ(ch)) return(FALSE); } return(TRUE); } // // ::= | "." // ::= | // ::= | "\" // static BOOL pValidateDotString(char *lpszStart, DWORD dwLength) { char ch; BOOL fChar = FALSE; if (!dwLength) return(FALSE); while (dwLength--) { ch = *lpszStart++; if (ch == '\\') { // Escape pair if (!dwLength) return(FALSE); dwLength--; lpszStart++; fChar = TRUE; } else if (ch == '.') { // 1) Must not start with a dot, // 2) Consecutive dots are not allowed if (!fChar) return(FALSE); // Reset the flag fChar = FALSE; } else if (pIsC(ch)) fChar = TRUE; else return(FALSE); } // Cannot end with a dot if (ch == '.') return(FALSE); return(TRUE); } // // Note: Original RFC 821: // ::= // ::= | // ::= | // ::= | | "-" // // Our implementation: // ::= | // ::= | | "-" | "_" // // Reasons: // 1) 3COM start their domains with a digit // 2) Some customers start their domain names with underscores, // and some comtain underscores. // static BOOL pValidateName(char *lpszStart, DWORD dwLength) { char ch; if (!dwLength) return(FALSE); while (dwLength--) { ch = *lpszStart++; if (pIsA(ch) || pIsD(ch) || ch == '-' || ch == '_') ; else return(FALSE); } return(TRUE); } // // ::= | // static BOOL pValidateLocalPart(char *lpszStart, DWORD dwLength) { if (!dwLength) return(FALSE); return(pValidateDotString(lpszStart, dwLength) || pValidateQuotedString(lpszStart, dwLength)); } // // ::= | "#" | "[" "]" // static BOOL pValidateElement(char *lpszStart, DWORD dwLength) { char ch; if (!dwLength) return(FALSE); ch = *lpszStart; if (ch == '#') // This is the # form return(pValidateNumber(lpszStart+1, dwLength-1)); else if (ch == '[') { if (lpszStart[dwLength-1] != ']') return(FALSE); // This is a domain literal return(pValidateDotnum(lpszStart+1, dwLength-2)); } // Validate as a name return(pValidateName(lpszStart, dwLength)); } // // sub-domain ::= let-dig *(ldh-str) // ldh-str = *( Alpha / Digit / "-" ) let-dig // let-dig = Alpha / Digit // static BOOL pValidateDRUMSSubDomain(char *lpszStart, DWORD dwLength) { unsigned char ch; DWORD ec; if (!dwLength) return(FALSE); // validate all of the characters in the name while (dwLength--) { ch = (unsigned char) *lpszStart++; // this list of characters comes from NT, dnsvldnm.doc. we // also allow #, [, and ] if ((ch >= 1 && ch <= 34) || (ch >= 36 && ch <= 41) || (ch == 43) || (ch == 44) || (ch == 47) || (ch >= 58 && ch <= 64) || (ch == 92) || (ch == 94) || (ch == 96) || (ch >= 123)) { return FALSE; } } //while //We have a valid subdomain return (TRUE); } // // ====================================================== // BOOL FindNextUnquotedOccurrence(char *lpszString, DWORD dwStringLength, char cSearch, char **ppszLocation) { BOOL fNotFound = FALSE; *ppszLocation = pFindNextUnquotedOccurrence(lpszString, dwStringLength,cSearch, acOpen,acClose, &fNotFound); if (!*ppszLocation) { // If failed but not because of not found, then bad line if (!fNotFound) { SetLastError(ERROR_INVALID_DATA); return FALSE; } } return TRUE; } // // This function extracts an email address from the given command line // and returns the tail of the line after the address. Any angle braces // present will be included as part of the 821 address. The returned // address is not validated at all. // BOOL Extract821AddressFromLine( char *lpszLine, char **ppszAddress, DWORD *pdwAddressLength, char **ppszTail) { DWORD dwAddressLength = 0; char *pAddressEnd; BOOL fNotFound; TraceFunctEnter("Extract821AddressFromLine"); _ASSERT(lpszLine); _ASSERT(ppszAddress); _ASSERT(pdwAddressLength); _ASSERT(ppszTail); // Initialize *ppszAddress = lpszLine; *pdwAddressLength = 0; *ppszTail = lpszLine; // Routine checking if (!lpszLine || // Big enough for MAX_INTERNET_NAME + any options on mail from/rcpt to !pValidateStringPtr(lpszLine, MAX_INTERNET_NAME + 2000) || !ppszAddress || IsBadWritePtr(ppszAddress, sizeof(char *)) || !ppszTail || IsBadWritePtr(ppszTail, sizeof(char *)) || !pdwAddressLength || IsBadWritePtr(pdwAddressLength, sizeof(DWORD))) { SetLastError(ERROR_INVALID_DATA); TraceFunctLeave(); return(FALSE); } // Skip all leading spaces while (*lpszLine == ' ') lpszLine++; // The first unquoted space indicates the end of the address pAddressEnd = pFindNextUnquotedOccurrence(lpszLine, lstrlen(lpszLine), ' ', acOpen, acClose, &fNotFound); if (!pAddressEnd) { // If failed but not because of not found, then bad line if (!fNotFound) return(FALSE); // Space not found, the entire line is the address dwAddressLength = lstrlen(lpszLine); pAddressEnd = lpszLine + dwAddressLength; *ppszTail = pAddressEnd; } else { // Calculate the length dwAddressLength = (DWORD)(pAddressEnd - lpszLine); // Get the start of the tail, after all the spaces while (*pAddressEnd == ' ') pAddressEnd++; *ppszTail = pAddressEnd; } if (dwAddressLength < 1 || dwAddressLength > MAX_INTERNET_NAME) return(FALSE); *ppszAddress = lpszLine; *pdwAddressLength = dwAddressLength; DebugTrace((LPARAM)0, "Extracted \"%*s\"", dwAddressLength, lpszLine); TraceFunctLeave(); return(TRUE); } // // This function takes in a RFC 821 address with optional angle braces // and extracts the canonical form of the address. All at-domain-list // entries are removed. Angle braces will be matched and removed. // Mismatched angle braces are considered invalid. The returned address // will be in the "@" form. // // There must be no leading or trailing spaces included. // // jstamerj 1999/01/13 14:02:13: Modified to remove a trailing '.' from the portion of the address // BOOL ExtractCanonical821Address( char *lpszAddress, DWORD dwAddressLength, char **ppszCanonicalAddress, DWORD *pdwCanonicalAddressLength) { char *pAddressStart; BOOL fNotFound; TraceFunctEnter("ExtractCanonical821Address"); _ASSERT(lpszAddress); _ASSERT(ppszCanonicalAddress); _ASSERT(pdwCanonicalAddressLength); // Initialize *ppszCanonicalAddress = lpszAddress; *ppszCanonicalAddress = 0; // Routine checking if (!lpszAddress || // Big enough for MAX_INTERNET_NAME + any options on mail from/rcpt to !pValidateStringPtr(lpszAddress, MAX_INTERNET_NAME + 2000) || !ppszCanonicalAddress || IsBadWritePtr(ppszCanonicalAddress, sizeof(char *)) || !pdwCanonicalAddressLength || IsBadWritePtr(pdwCanonicalAddressLength, sizeof(DWORD))) { SetLastError(ERROR_INVALID_DATA); TraceFunctLeave(); return(FALSE); } // See how many layers of nesting we have, and match // each pair of angle braces while (*lpszAddress == '<') { if (!dwAddressLength--) return(FALSE); if (lpszAddress[dwAddressLength] != '>') return(FALSE); if (!dwAddressLength--) return(FALSE); lpszAddress++; } // Next, skip all at-domain-list entries and get to // the meat of the address do { // Skip all leading spaces while (*lpszAddress == ' ') { lpszAddress++; if (!dwAddressLength--) return(FALSE); } //skip all the trailing spaces while (*(lpszAddress + dwAddressLength - 1) == ' ') { if (!dwAddressLength--) return(FALSE); } // Initialize lest it falls through right away pAddressStart = lpszAddress; if (*lpszAddress == '@') { // Yep, there's a domain route there ... // Skip it ... pAddressStart = pFindNextUnquotedOccurrence(lpszAddress, dwAddressLength, ',', acOpen, acClose, &fNotFound); if (!pAddressStart) { if (!fNotFound) return(FALSE); // No comma, now see if we get a semicolon pAddressStart = pFindNextUnquotedOccurrence(lpszAddress, dwAddressLength, ':', acOpen, acClose, &fNotFound); if (!pAddressStart) { // No semicolon either, this is a bad address return(FALSE); } // This is a semicolon, so we break out pAddressStart++; dwAddressLength -= (DWORD)(pAddressStart - lpszAddress); break; } // We have a comma, we let it iterate pAddressStart++; dwAddressLength -= (DWORD)(pAddressStart - lpszAddress); lpszAddress = pAddressStart; } else break; } while (dwAddressLength); // Skip all leading spaces while (*pAddressStart == ' ') { pAddressStart++; if (!dwAddressLength--) return(FALSE); } if((dwAddressLength > 1) && // Must be at least 2 for the address "@." (pAddressStart[dwAddressLength-1] == '.')) { // // jstamerj 1999/01/13 14:05:39: // If the domain part of the address has a trailing '.', do // not count it in the canonical length // LPSTR pDomain; BOOL fNotFound; // Find the domain pDomain = pFindNextUnquotedOccurrence( pAddressStart, dwAddressLength - 1, '@', acOpen, acClose, &fNotFound); // // If we found the '@' and the '.' is after the '@' (it must // be if we really found it), then shorten the canonical // address so that it doesn't include '.' // if((fNotFound == FALSE) && (&(pAddressStart[dwAddressLength]) > pDomain)) dwAddressLength--; } if (dwAddressLength < 1 || dwAddressLength > MAX_INTERNET_NAME) return(FALSE); // Fill in the output *ppszCanonicalAddress = pAddressStart; *pdwCanonicalAddressLength = dwAddressLength; DebugTrace((LPARAM)0, "Extracted \"%*s\"", dwAddressLength, pAddressStart); TraceFunctLeave(); return(TRUE); } // // This function takes in a RFC 821 domain in canonical form // and validates it according to the RFC 821 grammar // (some modifications for real-life scenarios) // // ::= | "." // BOOL Validate821Domain( char *lpszDomain, DWORD dwDomainLength) { char *pSubdomainOffset; DWORD dwSubdomainLength; BOOL fNotFound; TraceFunctEnter("Validate821Domain"); _ASSERT(lpszDomain); // Routine checking if (!lpszDomain || !pValidateStringPtr(lpszDomain, MAX_INTERNET_NAME+1)) { SetLastError(ERROR_INVALID_DATA); TraceFunctLeave(); return(FALSE); } // Find each subdomain do { pSubdomainOffset = pFindNextUnquotedOccurrence(lpszDomain, dwDomainLength, '.', acOpen, acClose, &fNotFound); if (!pSubdomainOffset) { if (!fNotFound) { SetLastError(ERROR_INVALID_DATA); return(FALSE); } // Not found and nothing left, domain ends with a dot, invalid. if (!dwDomainLength) { SetLastError(ERROR_INVALID_DATA); return(FALSE); } // No domain, so email alias is all there is dwSubdomainLength = dwDomainLength; } else { // Calculate domain parameters dwSubdomainLength = (DWORD)(pSubdomainOffset - lpszDomain); // Adjust for the dot dwDomainLength--; } // Cannot allow leading dot or consecutive dots if (!dwSubdomainLength) { SetLastError(ERROR_INVALID_DATA); return(FALSE); } // Check each subdomain as an element if (!pValidateElement(lpszDomain, dwSubdomainLength)) { SetLastError(ERROR_INVALID_DATA); return(FALSE); } // Adjust the length and pointers dwDomainLength -= dwSubdomainLength; // Skip past dot and scan again lpszDomain = pSubdomainOffset + 1; } while (dwDomainLength); // Make sure no dot's found, either if (!fNotFound) { // If a dot's found, the domain ends with a dot and it's uncool. SetLastError(ERROR_INVALID_DATA); return(FALSE); } TraceFunctLeave(); return(TRUE); } // // This function takes in a DRUMS domain in canonical form // and validates it strictly, according to the DRUMS grammar // // Domain ::= sub-domain 1*("." sub-domain) | address-literal // address-literal ::= "[" IPv4-address-literal | // IPv6-address-literal | General-address-literal "]" // IPv4-address-literal ::= snum 3("." snum) // IPv6-address-literal ::= "IPv6" SP <> // General-address-literal ::= Standardized-tag SP String // Standardized-tag ::= String (Specified in a standards-track RFC // and registered with IANA) // snum = one, two, or three digits representing a decimal // integer value in the range 0 through 255 BOOL ValidateDRUMSDomain( char *lpszDomain, DWORD dwDomainLength) { char *pSubdomainOffset; DWORD dwSubdomainLength; BOOL fNotFound; char *szEndofString; TraceFunctEnter("Validate821Domain"); _ASSERT(lpszDomain); // Routine checking if (!dwDomainLength || dwDomainLength > MAX_INTERNET_NAME) return(FALSE); if (!lpszDomain || !pValidateStringPtr(lpszDomain, MAX_INTERNET_NAME+1)) { SetLastError(ERROR_INVALID_DATA); TraceFunctLeave(); return(FALSE); } // Skip all leading spaces while (*lpszDomain == ' ') lpszDomain++; //It has to be either in address-literal format or subdomain format // if (*lpszDomain == '[') { //It is an Address literal //Skip trailing white space szEndofString = &lpszDomain[lstrlen(lpszDomain) - 1]; while(*szEndofString == ' ') szEndofString--; if (*szEndofString != ']') return(FALSE); // This is a domain literal return(pValidateDotnum(lpszDomain+1, dwDomainLength-2)); } else { //This is in subdomain format do { pSubdomainOffset = pFindNextUnquotedOccurrence(lpszDomain, dwDomainLength, '.', acOpen, acClose, &fNotFound); if (!pSubdomainOffset) { if (!fNotFound) { SetLastError(ERROR_INVALID_DATA); return(FALSE); } // Not found and nothing left, domain ends with a dot, invalid. if (!dwDomainLength) { SetLastError(ERROR_INVALID_DATA); return(FALSE); } // No domain, so email alias is all there is dwSubdomainLength = dwDomainLength; } else { // Calculate domain parameters dwSubdomainLength = (DWORD)(pSubdomainOffset - lpszDomain); // Adjust for the dot //NimishK : **Check with Keith if this should be subdomain. dwDomainLength--; } // Cannot allow leading dot or consecutive dots if (!dwSubdomainLength) { SetLastError(ERROR_INVALID_DATA); return(FALSE); } // Check each subdomain if (!pValidateDRUMSSubDomain(lpszDomain, dwSubdomainLength)) { SetLastError(ERROR_INVALID_DATA); return(FALSE); } // Adjust the length and pointers dwDomainLength -= dwSubdomainLength; // Skip past dot and scan again lpszDomain = pSubdomainOffset + 1; } while (dwDomainLength); // Make sure no dot's found, either if (!fNotFound) { // If a dot's found, the domain ends with a dot and it's uncool. SetLastError(ERROR_INVALID_DATA); return(FALSE); } TraceFunctLeave(); return(TRUE); } TraceFunctLeave(); return(TRUE); } // // This function takes in a RFC 821 address in canonical form // ( ["@" ]) and validates it according to the // RFC 821 grammar (some modifications for real-life scenarios) // BOOL Validate821Address( char *lpszAddress, DWORD dwAddressLength) { char *pDomainOffset; DWORD dwEmailLength; DWORD dwDomainLength; BOOL fNotFound; TraceFunctEnter("Validate821Address"); _ASSERT(lpszAddress); // Routine checking if (!lpszAddress || !pValidateStringPtr(lpszAddress, MAX_INTERNET_NAME+1)) { SetLastError(ERROR_INVALID_DATA); TraceFunctLeave(); return(FALSE); } // Find the domain pDomainOffset = pFindNextUnquotedOccurrence(lpszAddress, dwAddressLength, '@', acOpen, acClose, &fNotFound); if (!pDomainOffset) { if (!fNotFound) { SetLastError(ERROR_INVALID_DATA); return(FALSE); } // No domain, so email alias is all there is dwEmailLength = dwAddressLength; } else { // Calculate domain parameters dwEmailLength = (DWORD)(pDomainOffset - lpszAddress); dwDomainLength = dwAddressLength - dwEmailLength - 1; pDomainOffset++; } // Do the check for email name if (!pValidateLocalPart(lpszAddress, dwEmailLength)) { SetLastError(ERROR_INVALID_DATA); return(FALSE); } // Now check domain, if applicable if (pDomainOffset) { return(Validate821Domain(pDomainOffset, dwDomainLength)); } TraceFunctLeave(); return(TRUE); } // // This function takes in a RFC 821 address in canonical form // ( ["@" ]) and extracts the domain part // BOOL Get821AddressDomain( char *lpszAddress, DWORD dwAddressLength, char **ppszDomain) { char *pDomainOffset; BOOL fNotFound = FALSE; BOOL fReturn = TRUE; TraceFunctEnter("Get821AddressDomain"); _ASSERT(lpszAddress); // Find the domain pDomainOffset = pFindNextUnquotedOccurrence(lpszAddress, dwAddressLength, '@', acOpen, acClose, &fNotFound); if (!pDomainOffset && !fNotFound) { SetLastError(ERROR_INVALID_DATA); fReturn = FALSE; goto Exit; } if (fNotFound) { *ppszDomain = NULL; goto Exit; } *ppszDomain = pDomainOffset + 1; // Validate that the domain part is <= 255 chars if ((dwAddressLength - (*ppszDomain - lpszAddress)) > 255) { *ppszDomain = NULL; SetLastError(ERROR_INVALID_DATA); fReturn = FALSE; goto Exit; } Exit: TraceFunctLeave(); return fReturn; }