/*++ Copyright (c) 1992-1993 Microsoft Corporation Module Name: Prompt.c Abstract: PortUasDefaultPromptForNewName. Author: John Rogers (JohnRo) 29-Oct-1992 Environment: Portable to any flat, 32-bit environment. (Uses Win32 typedefs.) Requires that sizeof(DWORD) >= sizeof(pointer) (for FormatMessageW). Requires ANSI C extensions: slash-slash comments, long external names. Revision History: 29-Oct-1992 JohnRo Created for RAID 9020: setup: PortUas fails ("prompt on conflicts" version). 26-Jan-1993 JohnRo RAID 8683: PortUAS should set primary group from Mac parms. 30-Jul-1993 JohnRo RAID NTISSUE 2260: PortUAS returns a NetUserAdd error=1379 with local group. Do a little checking on values set by FormatMessageA(). Made changes suggested by PC-LINT 5.0 24-Aug-1993 JohnRo RAID 2822: PortUAS maps chars funny. (Work around FormatMessageA bug.) 01-Sep-1993 JohnRo Add PortUAS /log:filename switch for Cheetah. PC-LINT found a bug: PUAS_GROUP_EXISTS_AS_A_LOCALGROUP wasn't used yet. Fix output intended to stderr. Made more changes suggested by PC-LINT 5.0 --*/ // These must be included first: #include // Needed by #include // (Needed with nt.h and windows.h) #include // (Needed with ntrtl.h and windows.h) #include // IN, LPWSTR, BOOL, etc. #include // NET_API_STATUS, UNLEN, GNLEN. // These may be included in any order: #include #include // NetpIsUserNameValid(), etc. #include // NetpAssert(), FORMAT_ equates, etc. #include // NetpErrNoToApiStatus(), MAX_NETLIB_MESSAGE_ARG, etc. //#include // write #include // va_list, va_start(), va_end(), va_arg(). #include // feof(), ferror(), printf(), stdin, etc. #include // REASON_ equates. #include // My prototype, Verbose. #include // NetpAllocWStrFromWStr(). #include // iswdigit(), wcsncmp(), etc. #include // ERROR_ equates, NO_ERROR. #include "nlstxt.h" // NLS message ID's. extern TCHAR ConBuf[]; extern int MyWriteConsole(int fOutOrErr, int cch); extern int FileIsConsole(HANDLE fh); // // Globals (may also be set by PortUasParseCommandLine): // HANDLE PortUasGlobalLogFileHandle = INVALID_HANDLE_VALUE; /*** NlsPutMsg - Print a message to a handle * * Purpose: * PutMsg takes the given message number from the * message table resource, and displays it on the requested * handle with the given parameters (optional) * * unsigned PutMsg(unsigned Handle, unsigned MsgNum, ... ) * * Args: * Handle - the handle to print to (must be STDOUT or STDERR) * MsgNum - the number of the message to print * Arg1 [Arg2...] - additonal arguments for the message as necessary * * Returns: * The number of characters printed. * */ /*lint -save -e579 */ // Don't complain about unwidened before ... USHORT NlsPutMsg(USHORT Handle, USHORT usMsgNum, ... ) { LPTSTR AllocatedStrings[ MAX_NETLIB_MESSAGE_ARG+1 ]; // 0 unused. NET_API_STATUS ApiStatus; va_list arglist; BOOL ArgListInUse = FALSE; LPWSTR FormattedOutput = NULL; DWORD Index; DWORD ModifiedArgs[ MAX_NETLIB_MESSAGE_ARG+1 ]; // 0 ignored. DWORD msglen; LPSTR NarrowFormat = NULL; LPWSTR UnicodeFormat = NULL; // Unicode format, modified as we go. NetpAssert( (Handle == STDOUT) || (Handle == STDERR) ); va_start(arglist, usMsgNum); ArgListInUse = TRUE; for (Index=0; Index <= MAX_NETLIB_MESSAGE_ARG; ++Index) { AllocatedStrings[Index] = NULL; } // // As of this writing (19-Aug-1993), there is a bug below FormatMessageA(), // where the "Unicode-to-ANSI translation" just truncates Unicode chars to // 8 bits. So, we need to avoid that by using FormatMessageA to get the // message, convert it to Unicode, and use FormatMessageW to do the actual // formatting. // // Get the format string (as NarrowFormat). // msglen = FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, NULL, // source usMsgNum, (DWORD) 0, // Default country ID. (LPVOID) &NarrowFormat, // Alloc and set pointer. 0, &arglist ); if (msglen == 0) { ApiStatus = GetLastError(); (VOID) WriteToCon( TEXT("PortUAS.exe: FormatMessageA failed: ") TEXT(FORMAT_API_STATUS) TEXT("\n"), ApiStatus ); NetpAssert( ApiStatus != NO_ERROR ); goto Cleanup; } NetpAssert( NarrowFormat != NULL ); if (NarrowFormat == NULL) { msglen = 0; goto Cleanup; } // // OK, Now convert the format string to Unicode. // We'll modify the Unicode format string (in place) below... // UnicodeFormat = NetpAllocWStrFromStr( NarrowFormat ); if (UnicodeFormat == NULL) { // Rats, probably ran out of memory. Well, we tried. msglen = 0; goto Cleanup; } // // FormatMessageA() just walked the va_list. We're going to walk it // again, looking for args to convert. So, tell C runtime... // NetpAssert( ArgListInUse ); va_end(arglist); va_start(arglist, usMsgNum); // // For each numbered arg (1..MAX_NETLIB_MESSAGE_ARG), perhaps modify UnicodeFormat in // place and come up with a narrow string for its arg. For other args, // just copy from va_list format to an array we can pass to // FormatMessageW(). // // Things we handle: // %1!lu! // %2 // %5!08lX! // %16!ws! // for (Index=1; Index <= MAX_NETLIB_MESSAGE_ARG; ++Index) { LPWSTR ThisFormat; ThisFormat = (LPWSTR) NetpFindNumberedFormatInWStr( UnicodeFormat, Index ); // Arg number (1=first). if (ThisFormat == NULL) { break; // Just last numbered arg (if any). } NetpAssert( ThisFormat[0] == L'%' ); NetpAssert( ThisFormat[1] != L'0' ); // Leading zero would confuse. NetpAssert( iswdigit( ThisFormat[1] ) ); NetpAssert( wcslen( ThisFormat ) >= 2 ); // At least "%1". // Skip "%1" or "%16"... ++ThisFormat; // Skip percent sign. NetpSkipWDigits( ThisFormat ); // Parse format itself. if (ThisFormat[0] == L'!') { ++ThisFormat; // Skip leading BANG. // Skip possible leading digits in "08lX"... NetpSkipWDigits( ThisFormat ); if ( wcsncmp( ThisFormat, (LPWSTR) L"ws", 2 ) == 0 ) { LPWSTR OriginalArg; OriginalArg = va_arg( arglist, LPWSTR ); AllocatedStrings[ Index ] = OriginalArg; // Modify format AND arg for this one. ThisFormat[0] = TEXT('h'); ModifiedArgs[ Index ] = (DWORD) AllocatedStrings[ Index ]; ThisFormat += 2; // Skip "hs". } else if ( ThisFormat[0] == TEXT('l') ) { DWORD TempDword; if ( wcsncmp( ThisFormat, (LPWSTR) L"lu", 2 ) == 0 ) { TempDword = va_arg( arglist, DWORD ); ModifiedArgs[ Index ] = TempDword; } else if ( wcsncmp( ThisFormat, (LPWSTR) L"lX", 2 ) == 0 ) { TempDword = va_arg( arglist, DWORD ); ModifiedArgs[ Index ] = TempDword; } else if ( wcsncmp( ThisFormat, (LPWSTR) L"lx", 2 ) == 0 ) { TempDword = va_arg( arglist, DWORD ); ModifiedArgs[ Index ] = TempDword; } else { NetpAssert( FALSE ); msglen = 0; goto Cleanup; } ThisFormat += 2; // Skip "lu", etc. } else if ( wcsncmp( ThisFormat, (LPWSTR) L"s", 1 ) == 0 ) { LPTSTR OriginalArg = va_arg( arglist, LPTSTR ); //FARBUGBUG '%s' is Unicode to FormatMessageW. ModifiedArgs[ Index ] = (DWORD) OriginalArg; ThisFormat += 1; // Skip "s", etc. } else if ( wcsncmp( ThisFormat, (LPWSTR) L"hs", 2 ) == 0 ) { LPTSTR OriginalArg = va_arg( arglist, LPTSTR ); ModifiedArgs[ Index ] = (DWORD) OriginalArg; ThisFormat += 2; // Skip "hs", etc. } else { NetpAssert( FALSE ); msglen = 0; goto Cleanup; } // Must be to trailing BANG here. NetpAssert( L'!' == *ThisFormat ); if (*ThisFormat != BANG) { msglen = 0; goto Cleanup; } ++ThisFormat; // Skip trailing BANG } else { //FARBUGBUG '%s' is Unicode to FormatMessageW. LPTSTR OriginalArg = va_arg( arglist, LPTSTR ); ModifiedArgs[ Index ] = (DWORD) OriginalArg; ThisFormat += 1; // Skip "s", etc. } } // // Use modified Unicode format string and modified args, to format the // message. // msglen = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, UnicodeFormat, // source of format string usMsgNum, (DWORD) 0, // Default country ID. (LPVOID) &FormattedOutput, // Alloc and set pointer. 0, (LPVOID) &ModifiedArgs[1] ); // Array of 32-bit args. if (msglen == 0) { ApiStatus = GetLastError(); (VOID) WriteToCon( TEXT("PortUAS.exe: FormatMessageW failed: ") TEXT(FORMAT_API_STATUS) TEXT("\n"), ApiStatus ); NetpAssert( ApiStatus != NO_ERROR ); // BUGBUG: Event log this? goto Cleanup; } NetpAssert( FormattedOutput != NULL ); if (FormattedOutput == NULL) { msglen = 0; goto Cleanup; } // // Print the message. // { if (Handle == STDOUT) { msglen = WriteToCon(TFORMAT_LPWSTR, FormattedOutput); } else { msglen = _stprintf( ConBuf, TFORMAT_LPWSTR, FormattedOutput); if (msglen >= 0) msglen = MyWriteConsole(STDERR, msglen); else msglen = 0; } } if ( msglen == ((USHORT)-1) ) { msglen = 0; } // // If we're logging stuff, then log a copy of this message too. // if (PortUasGlobalLogFileHandle != INVALID_HANDLE_VALUE) { ApiStatus = PortUasWriteToLogFile( PortUasGlobalLogFileHandle, (LPCTSTR) FormattedOutput ); if (ApiStatus != NO_ERROR) { msglen = 0; } } Cleanup: if (msglen == 0) { // BUGBUG: event log this? (VOID) WriteToCon( TEXT("PortUAS.exe: UNEXPECTED ERROR.\n") ); } for (Index=0; Index<=MAX_NETLIB_MESSAGE_ARG; ++Index) { if (AllocatedStrings[Index] != NULL) { (VOID) NetApiBufferFree( AllocatedStrings[Index] ); } } if (FormattedOutput != NULL) { (VOID) LocalFree( FormattedOutput ); } if (NarrowFormat != NULL) { (VOID) LocalFree( NarrowFormat ); } if (UnicodeFormat != NULL) { (VOID) LocalFree( UnicodeFormat ); } if (ArgListInUse) { va_end(arglist); } return( (USHORT) msglen); } /*lint -restore */ DBGSTATIC NET_API_STATUS PortUasGetString( OUT LPWSTR BufferW, IN DWORD BufLenW // number of chars for string and NULLC. ) { NET_API_STATUS ApiStatus; char * BufferA = NULL; DWORD BufLenA = BufLenW + 1; // room for str, \n, \0 char * NewLine; char * Result; NetpAssert( BufferW != NULL ); NetpAssert( BufLenW > 0 ); if (FileIsConsole(GetStdHandle(STD_INPUT_HANDLE))) { ApiStatus = ReadConsole(GetStdHandle(STD_INPUT_HANDLE), BufferW, BufLenW, &BufLenA, 0); if (ApiStatus) { // // Get rid of cr/lf // if (wcschr(BufferW, RETURN) == NULL) { if (wcschr(BufferW, NEWLINE)) *wcschr(BufferW, NEWLINE) = NULLC; } else *wcschr(BufferW, RETURN) = NULLC; ApiStatus = NO_ERROR; goto Cleanup; } else { ApiStatus = GetLastError(); } } // // Allocate an 8-bit version. // ApiStatus = NetApiBufferAllocate( BufLenA, // byte count (LPVOID *) (LPVOID) & BufferA); if (ApiStatus != NO_ERROR) { NetpAssert( BufferA == NULL ); goto Cleanup; } else { NetpAssert( BufferA != NULL ); } // // Read user's input (from stdin). // fgets will return a buffer of the form "flarp\n\0". // Result = fgets( BufferA, (int) BufLenA, stdin); // // Check for end of file or error. // if (Result == NULL) { if (ferror(stdin)) { ApiStatus = NetpErrNoToApiStatus( errno ); NetpAssert( ApiStatus != NO_ERROR ); goto Cleanup; } else { NetpAssert( feof(stdin) ); ApiStatus = ERROR_HANDLE_EOF; goto Cleanup; } } // // Make sure string isn't too long, and get rid of newline. // NewLine = strchr( BufferA, '\n'); if (NewLine == NULL) { ApiStatus = ERROR_INVALID_DATA; // string was too long. goto Cleanup; } NetpAssert( *(NewLine+1) == '\0' ); *NewLine = '\0'; // // copy and convert to UNICODE. // NetpCopyStrToWStr( BufferW, // dest: unicode (LPVOID) BufferA ); // src: string in default LAN codepage ApiStatus = NO_ERROR; Cleanup: if (BufferA != NULL) { (VOID) NetApiBufferFree( BufferA ); } if (Verbose) { if (ApiStatus==NO_ERROR) { NetpKdPrint(( PREFIX_PORTUAS "PortUasGetString: " " output string is '" FORMAT_LPWSTR "'.\n", BufferW )); } NetpKdPrint(( PREFIX_PORTUAS "PortUasGetString: " " returning status " FORMAT_API_STATUS ".\n", ApiStatus )); } return (ApiStatus); } // PortUasGetString NET_API_STATUS PortUasComplainAboutBadName( IN LPWSTR OldName, IN BOOL ThisIsUserName, // TRUE for user name, FALSE for group name IN DWORD Reason // REASON_ equates from PortUAS.h ) { NET_API_STATUS ApiStatus; //LPTSTR ReasonText = NULL; USHORT ReasonText = 0; // // Come up with reason text (WriteToCon format string). // switch (Reason) { case REASON_BAD_NAME_SYNTAX: //ReasonText = "bad name syntax '" FORMAT_LPWSTR "'"; ReasonText = PUAS_BAD_NAME_SYNTAX; break; case REASON_CONFLICT_WITH_DOMAIN: //ReasonText = "name '" FORMAT_LPWSTR "' conflicts with a domain name"; ReasonText = PUAS_NAME_CONFLICTS_WITH_A_DOMAIN_NAME; break; case REASON_CONFLICT_WITH_GROUP: //ReasonText = "User '" FORMAT_LPWSTR "' exists as a group"; ReasonText = PUAS_USER_EXISTS_AS_A_GROUP; break; case REASON_CONFLICT_WITH_USERNAME: //ReasonText = "group '" FORMAT_LPWSTR "' exists as a user"; ReasonText = PUAS_GROUP_EXISTS_AS_A_USER; break; #ifdef FAT8 case REASON_NAME_LONG_FOR_TEMP_REG: //ReasonText = "group '" FORMAT_LPWSTR "' name too long."; ReasonText = PUAS_GROUP_NAME_TOO_LONG; break; #endif case REASON_CONFLICT_WITH_LOCALGROUP: if (ThisIsUserName) { //ReasonText = "User '" FORMAT_LPWSTR "' exists as a local group"; ReasonText = PUAS_USER_EXISTS_AS_A_LOCALGROUP; } else { ReasonText = PUAS_GROUP_EXISTS_AS_A_LOCALGROUP; } break; default: NetpKdPrint(( PREFIX_PORTUAS "PortUasComplainAboutBadName: " "got invalid value " FORMAT_DWORD " for reason.\n", Reason )); ApiStatus = ERROR_INVALID_PARAMETER; goto Cleanup; } //NetpAssert( ReasonText != NULL ); NetpAssert( ReasonText != 0 ); //(VOID) WriteToCon( TEXT("\n\n*** Bad %s name *** "), // ( ThisIsUserName ? TEXT("user") : TEXT("group") ) ); if (ThisIsUserName) { (VOID) NlsPutMsg(STDOUT, PUAS_BAD_USER_NAME); } else { (VOID) NlsPutMsg(STDOUT, PUAS_BAD_GROUP_NAME); } //(VOID) WriteToCon( ReasonText, OldName ); (VOID)NlsPutMsg(STDOUT, ReasonText, OldName); ApiStatus = NO_ERROR; Cleanup: return (ApiStatus); } DBGSTATIC BOOL IsCharInString ( IN WCHAR wch, IN WCHAR * wszString ) { WCHAR * wszTemp = wszString; while (*wszTemp != (WCHAR)NULLC) { if (*wszTemp == wch) { return(TRUE); } ++wszTemp; } return(FALSE); } NET_API_STATUS PortUasDefaultPromptForNewName( IN LPWSTR OldName, IN BOOL ThisIsUserName, // TRUE for user name, FALSE for group name IN DWORD Reason, // REASON_ equates from PortUAS.h OUT LPWSTR * NewName, // alloc w/ NetApiBufferAllocate(). OUT BOOL * IgnoreThis, OUT BOOL * ForceIgnoreFromNowOn ) { NET_API_STATUS ApiStatus; WCHAR Flag[3]; LPWSTR NameToComplainAbout = OldName; // OldName first time, but... WCHAR TempName[UNLEN+2]; // space for user name or group name static WCHAR * szYesNoForceChars[3] = {NULL, NULL, NULL}; // 0(Yes), 1(No), 2(Force) UINT i; BOOL Valid; // Get the localised yes, no and force characters. // IMPORTANT!!! Yes, No, and Force characters must be // stored in sequentially-numbered messages in // the MSGTABLE resource! for (i=0; i<=2; ++i) { if (szYesNoForceChars[i] == NULL) { if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, NULL, (PUAS_YES_CHARS + i), 0L, // Default country ID. (LPTSTR)&(szYesNoForceChars[i]), 0, NULL ) == 0) { UNEXPECTED_MSG( "FormatMessage", GetLastError() ); } } } NetpAssert( OldName != NULL ); NetpAssert( (ThisIsUserName==TRUE) || (ThisIsUserName==FALSE) ); NetpAssert( NewName != NULL ); NetpAssert( IgnoreThis != NULL ); NetpAssert( ForceIgnoreFromNowOn != NULL ); NetpAssert( UNLEN >= GNLEN ); // Make TempName larger if this fails. *NewName = NULL; // don't confuse caller on error. Valid = FALSE; while ( !Valid ) { // // Explain what's wrong... // ApiStatus = PortUasComplainAboutBadName( OldName, ThisIsUserName, Reason ); if (ApiStatus != NO_ERROR) { UNEXPECTED_MSG( "PortUasComplainAboutBadName", ApiStatus ); goto Cleanup; } // // Prompt explaining what's wrong and what they can do... // //(VOID) WriteToCon( // TEXT("\n") // TEXT("Do you want to enter a new name?\n") // TEXT("Enter Y for yes, N to ignore this name, or F to force ") // TEXT("ignore from now on.\n") ); (VOID)NlsPutMsg(STDOUT, PUAS_DO_YOU_WANT_A_NEW_NAME); ApiStatus = PortUasGetString( Flag, 3 ); // get str (max 1 char & nul) switch (ApiStatus) { case NO_ERROR: break; case ERROR_INVALID_DATA: // Name too long gets this. Valid = FALSE; continue; // loop again. case ERROR_HANDLE_EOF: *IgnoreThis = TRUE; *ForceIgnoreFromNowOn = TRUE; ApiStatus = NO_ERROR; goto Cleanup; default: UNEXPECTED_MSG( "PortUasGetString(flag)", ApiStatus ); goto Cleanup; } NetpAssert( ApiStatus == NO_ERROR ); //switch (Flag[0]) { // //case TEXT('Y'): /*FALLTHROUGH*/ //case TEXT('y'): if (IsCharInString(Flag[0],szYesNoForceChars[0])) { //(VOID) WriteToCon( TEXT("Enter a new name:\n") ); (VOID)NlsPutMsg(STDOUT, PUAS_ENTER_A_NEW_NAME); ApiStatus = PortUasGetString( TempName, UNLEN+2 ); switch (ApiStatus) { case NO_ERROR: break; case ERROR_INVALID_DATA: // Name too long gets this. NameToComplainAbout = TempName; Reason = REASON_BAD_NAME_SYNTAX; Valid = FALSE; continue; // Loop again. case ERROR_HANDLE_EOF: *IgnoreThis = TRUE; *ForceIgnoreFromNowOn = TRUE; ApiStatus = NO_ERROR; goto Cleanup; default: UNEXPECTED_MSG( "PortUasGetString(name)", ApiStatus ); goto Cleanup; } if (TempName[0] == NULLC) { NameToComplainAbout = TempName; Reason = REASON_BAD_NAME_SYNTAX; Valid = FALSE; continue; // Loop again. } if (ThisIsUserName) { if ( !NetpIsUserNameValid( TempName ) ) { NameToComplainAbout = TempName; Reason = REASON_BAD_NAME_SYNTAX; Valid = FALSE; continue; // Loop again. } } else { if ( !NetpIsGroupNameValid( TempName ) ) { NameToComplainAbout = TempName; Reason = REASON_BAD_NAME_SYNTAX; Valid = FALSE; continue; // Loop again. } } Valid = TRUE; *IgnoreThis = FALSE; *ForceIgnoreFromNowOn = FALSE; *NewName = NetpAllocWStrFromWStr( TempName ); if (*NewName == NULL) { ApiStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } break; } //case TEXT('N'): /*FALLTHROUGH*/ //case TEXT('n'): else if (IsCharInString(Flag[0],szYesNoForceChars[1])) { Valid = TRUE; *IgnoreThis = TRUE; *ForceIgnoreFromNowOn = FALSE; break; } //case TEXT('F'): /*FALLTHROUGH*/ //case TEXT('f'): else if (IsCharInString(Flag[0],szYesNoForceChars[2])) { Valid = TRUE; *IgnoreThis = TRUE; *ForceIgnoreFromNowOn = TRUE; break; } //default: // Handle empty string here. else { Valid = FALSE; // Repeat prompt for flag and ask again. } } // while !Valid Cleanup: return (ApiStatus); } // PortUasDefaultPromptForNewName