AFSLogonShell

 

 

Rodney M. Dyer

x86 Systems Programmer

Mosaic Computing Group

William States Lee College of Engineering

University of North Carolina at Charlotte

Email: rmdyer@uncc.edu

Phone:  704-687-3518

Fax: 704-687-2352

 

February 26, 2003

 

Draft 1.0

 

 

 

 

 

What is it?

 

            AFSLogonShell provides an augmentation of the traditional AFS client authentication process under the Windows operating system.  AFSLogonShell allows an administrator to write their own child process in almost any compiled or scripted language that will be executed during the AFS authentication process at logon.  The child shell process will run as the SYSTEM user and will have available to it the username and password of the user logging on to the system.  The shell can also be made hidden so that the user never sees it.  Many things can be accomplished in the child shell that may help administrators in their administration of Windows machines that are using the AFS file system.

 

            AFSLogonShell is specifically a single C function that can be applied to the C source file called "afslogon.c".  The "afslogon.c" file is provided in the OpenAFS Windows client source code.  The AFSLogonShell function was made to be added into the file so that it is statically linked in.  No libraries or DLLs are required other than those already used by the AFS client.

 

 

 

 

 

Traditional AFS Logon Authentication

 

            When AFS logon authentication is enabled the Windows logon process calls upon the AFS logon authenticator for obtaining an AFS token for the user.  The AFS logon authenticator is simply a file called "afslogon.dll" that adheres to Microsoft's network provider interface standard and which should publicly export the function "NPLogonNotify".  Under normal circumstances the routines that reside in the "NPLogonNotify" function will use the user's username and password to obtain a Kerberos 4 ticket from the AFS Kerberos Authentication Server.  The routines then convert the ticket to a token and store that token into the user's token cache.  The "NPLogonNotify" function then exits allowing the Windows logon process to continue.  When the Windows logon process is completed, the "userinit.exe" program is called, which then sets up the user's environment.

 

            It is important to note that the user token obtained through Kerberos 4 authentication is placed into the token cache that is a part of the user's process space.  No other user can access that user's token (in theory).  In this case user SYSTEM does not have access to the user's token.  This is one reason that the AFSLogonShell function was written.

 

            The AFSLogonShell function can allow procedures to be written that run as user SYSTEM with a user's token.  We might need to do this for example to prepare the user's AFS file space before the user's profile is downloaded, or before folder redirection is done.  We also might want to reject the Kerberos 4 authentication outright and obtain for the user a token that was derived from Kerberos V.  There are plenty of uses for the AFSLogonShell function that have yet to be conceived.

 

            It is within the "NPLogonNotify" function that the AFSLogonShell function call is added.

 

            Here is a link to the Network Provider API...

 

            http://msdn.microsoft.com/library/default.asp?url=/library/en-us/security/security/network_provider_api.asp

 

            You can also just search on the MSDN site for "Network Provider".

 

 

 

 

 

How AFSLogonShell Works

 

            After several design changes it was decided that the best way to implement the AFSLogonShell function was so that it had little to no impact on the regular processing of the AFS authentication routines.  The theory is to allow the normal authentication process to take place then perform the child shell process before any error checking is done for that authentication.  So we have...

 

            1.  Perform normal Kerberos 4 authentication.

            2.  Call child shell process.

            3.  Check for error returned from 1.

           

            The nice thing about the way the AFSLogonShell is written is that we can control whether or not any error dialogs show up for any authentication problems encountered with regular authentication.  The reason we would want to do this is if we are performing our own authentication within our child shell.  If the child shell is executed successfully we may want to ignore the error reported from regular authentication.  The AFSLogonShell function is written so that the administrator can decide on how the error code from regular authentication is processed.

 

            I ended up with a function that requires only one line to add to the regular authentication code in the "NPLogonNotify" routine of "afslogon.c".  Here is the function line that needs to be added in...

 

                        code = dword_AFSLogonShell( hwndOwner, code, interactive, failSilently, define_AFSLogonShell_Mode_UseINIModes );

 

 

            If you've looked at the source code within the "NPLogonNotify" routine of "afslogon.c", you'll notice that one variable named "code", is used near the end for processing the result of the regular authentication.  Near the bottom of the "NPLogonNotify" routine we see that the "code" variable simply takes on a value of 0 (zero) in the case of success, or some other possible failure value.

 

            The AFSLogonShell function was written so that it can be run in several modes.  The simplest mode called "PassThrough" prevents the AFSLogonShell function from interfering with any normal authentication result.  In this manner the AFSLogonShell function will be "invisible" to the normal flow of processing.  The other modes of operation for the AFSLogonShell function allow the "code" value to be "controlled" by the administrator based on the execution or failure of the child process.

 

            Looking at the AFSLogonShell function line presented previously you see 5 parameters.  The first four parameters are regular variables used in the normal "NPLogonNotify" routine.  The AFSLogonShell function does not modify any of these variables within the function.  The fifth parameter is by default set to the define "define_AFSLogonShell_Mode_UseINIModes".  This is but one of 5 different defines that can be used as the fifth parameter of the AFSLogonShell function.  The fifth parameter of the AFSLogonShell function controls what the return value of the function will be.  For most circumstances the default value is fine and unless you are a programmer, should not be changed.  The define "define_AFSLogonShell_Mode_UseINIModes" allows the administrator to use a config setting at run-time to control what happens to the return value of the function.

 

            The AFSLogonShell function first tries to detect whether or not a system wide environment variable exists called "AFSLogonINI".  The "AFSLogonINI" environment variable points to a Windows INI file that contains the settings for executing the child shell process.  For example...

 

            AFSLogonINI=c:\admin\afslogon.ini

 

            The "afslogon.ini" file contains...

 

            [Config]

            Mode=PassThrough

            ExecString=c:\winnt\system32\cmd.exe /c c:\admin\hello_world.cmd

            ExecDirectory=c:\admin

            ExecTimeOut=40

            ExecShow=SW_SHOW

 

 

As you can see, the "afslogon.ini" file directs the AFSLogonShell function to execute a file called "cmd.exe" with the arguments "/c" and "hello_world.cmd".  The startup directory will be "c:\admin".  The AFSLogonShell function will then try to execute the file and wait 40 seconds for it to complete.

 

The "ExecTimeOut" is a value in seconds for "afslogon.dll" to wait for your child process to complete.  If the time goes beyond this value then it is assumed the child has locked-up or will never return.  In that case it is assumed the child has failed.  The AFSLogonShell function within the "afslogon.dll" is then allowed to continue with normal processing based on the "Mode" set.  Typically your shell process should take well less than 40 seconds, especially due to the following...

 

Important note:  There is also another time-out involved here.  When you first logon to the Windows machine the WinLogon process calls the "NPLogonNotify" routine within "afslogon.dll".   The Winlogon process gives the "afslogon.dll" a maximum time to complete.  If the "afslogon.dll" itself doesn't complete then the Winlogon process continues on with regular processing.  The WinLogon time-out is set default to 60 seconds unless overridden by a specific network provider.  You can override the max time by using...

 

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\NetworkProvider\RestoreTimeout = REG_DWORD n  

 

See the following for more information...

 

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/security/security/npgetcaps.asp

 

Specifically the WNNC_START member.

 

 

 

The "ExecShow" setting is for debugging.  Most of the time it will be set to "SW_HIDE", but for debugging, you can set it to "SW_SHOW".

 

The "Mode" setting directs the AFSLogonShell function to just pass the result of the regular AFS Kerberos 4 authentication on to the failure detection routines.  You can use two other options for "Mode" called "SuccessAlways" and "SuccessOnly".

 

The "SuccessAlways" option will always prevent the regular internal authentication from error'ing.  You might use this option if you are going to perform all of you own authentication within the child process that you create.

 

The "SuccessOnly" option is similar to the "SuccessAlways" option except that if the child process fails in any way, the regular authentication result will be passed on to the failure detection routines.  In the case that the child process completes successfully, the result will always prevent the regular internal processing from error'ing.

 

The AFSLogonShell function was written so that a INI file could be used instead of recompiling every time a change needed to be made to the code.  You are free as an administrator to change any of the settings in the INI file for executing your child process.

 

 

 

 

How to add the AFSLogonShell function to the "afslogon.c" source...

 

            Note:  I have a pre-compiled binary of the latest version of "afslogon.dll" from OpenAFS with the AFSLogonShell functioning added in.  I can't publicly release this since it would probably violate some license somewhere.  But, if you ask me nicely through personal email, I can probably send you a copy.

 

            Assuming you don't trust pre-compiled binaries and you understand C programming then we'll keep going...

 

            We'll start by testing the real version of the "afslogon.dll" that comes from the OpenAFS source code.  You need to first setup a working test PC that has the binary distribution of the OpenAFS client installed.  Make sure everything works as you expect it to.  You will note that the binary distribution of OpenAFS installs a working copy of the "afslogon.dll" into...

 

            "c:\program files\ibm\afs\client\program\afslogon.dll"

 

            After you have run your tests copy the "afslogon.dll" to "afslogon.dll_orig" to make a backup of it.

 

            Now, download the latest version of the OpenAFS source code and compile it for Windows.  After it is compiled copy the "afslogon.dll" that was created from the compile process to the test machine with the OpenAFS client on it.  Test the binary you just compiled on the test PC.  It should work as normal out-of-the box, if it doesn't, then something is probably wrong with your compile environment, or you are not using the same version of the binary distribution and source.

 

            Assuming you have everything tested and working now with the "real" OpenAFS source code, you can start by making a copy of the "afslogon.c" source.  I would copy it to "afslogon.c_orig" just to make a backup.  Now open up the real "afslogon.c" source and add the "begining through ending" of the "AFSLogonShell defs and function code" (at the bottom of this document) into the text near the top of the code, in an area just under all the variable declarations and defines, but before the function declarations begin.  Once you've done that you are ready to add the single line that calls the AFSLogonShell function.  It goes near the bottom of the "NPLogonNotify" routine.  Look for a section and add it in like this...

 

 

 

Beginning of NPLogonNotify code of "afslogon.c"

.

.

.

 

                        /* Wait just a little while and try again */

                 retryInterval = sleepInterval = DEFAULT_SLEEP_INTERVAL;

        }

                                       

        if (retryInterval < sleepInterval)

            sleepInterval = retryInterval;

                       

       Sleep(sleepInterval * 1000);

 

        retryInterval -= sleepInterval;

     }

 

  //------------------------------------------------------------------------

  // call AFSLogonShell

  //------------------------------------------------------------------------

  code = dword_AFSLogonShell( hwndOwner, code, interactive, failSilently, define_AFSLogonShell_Mode_UseINIModes );

 

 

  if (code) {

                char msg[128];

        sprintf(msg, "Integrated login failed: %s", reason);

               

       if (interactive && !failSilently)

            MessageBox(hwndOwner, msg, "AFS Logon", MB_OK);

       else {

 

.

.

.

End of NPLogonNotify code

 

 

            Once you've added in this code, compile it.  It should compile successfully, if not you may have done something wrong.  Assuming it did compile, grab the new "afslogon.dll" from the compile tree and copy it over to the PC you are using for testing.  Btw, the "afslogon.dll" is only "in-use" at logon, you should not have any trouble replacing it by using a regular copy.

 

            Now that you have it installed, you need to create the system-wide environment variable that points to the INI config file that tells the AFSLogonShell what to do.  You need to create a INI file similar to the one shown above that directs the processing of the AFSLogonShell function.  You might even create the "hello_world.cmd" script as above with the following in it for testing...

 

 

            hello_world.cmd file contents...

 

            @echo off

            echo Hello World!

            pause

 

 

            Assuming you've got everything ready to go.  You can logon as administrator and test the results.  With the "PassThough" mode enabled you should see a command window open up with the "Hello World!" text and a "Press any key to continue. . ." in the window.

 

            When you press a key the window will close and you might see the result of your trying to authenticate as admin which fails unless you've got an "Administrator" account setup for your AFS file system.

 

 

            The following environment variables are available for use from within your child process...

 

            WlMprNotifyDesktop=Winlogon

            WlMprNotifyDomain=DOMAIN

            WlMprNotifyLogonFlag=1

            WlMprNotifyLogonId=0:79047

            WlMprNotifyOldPasswordValid=0

            WlMprNotifyPassword=xxxxxxx

            WlMprNotifyStationHandle=0

            WlMprNotifyStationName=WinSta0

            WlMprNotifyUserName=abcdef

            WlMprNotifyWinlogonWindow=100072

 

            Obviously the "WlMprNotifyUserName" and "WlMprNotifyPassword" environment variables are very important for things we need to do in the shell.  We might want to get access to the user's home space before the profile downloads to make changes.  We might want to setup for folder redirection.  We might want to get a KRB V TGT and set the user a token based on that.  We can do WHATEVER WE WANT TO DO!

 

            To see the power in this change your "hello_world.cmd" code to read...

 

            hello_world.cmd file contents...

 

            @echo off

            echo Username=%WlMprNotifyUserName%

            echo Password=%WlMprNotifyPassword%

            pause

 

 

 

 

 

 

Security

 

            It is very important to understand that any child process you create will be run in the context of the SYSTEM account.  The SYSTEM account is the operating system itself, it's the account that services run as.  As the SYSTEM account you will be able to perform almost anything.  Because of this there are things that need to be protected...

 

*   You need to make sure no one can change the AFSLogonINI system-wide environment variable to point to something different.

 

*   You need to make sure that no one can modify the INI file that contains the config settings.

 

*   You need to make sure that no one has access to your child process binary or script.

 

*   You need to make sure your child process is "fool-proof", or at least "good-enough".

 

*   You need to make sure your child process doesn't "lock-up" even though there is a time-out protection mechanism built into the AFSLogonShell function.

 

*   You need to protect the "afslogon.dll" file.  That file could conceivably be changed by a user without your knowledge if it isn't protected.

 

*   You need to make sure if you are using scripts to not "echo" the environment variable contents to a pipe except when testing or debugging.  I've written a special app called "envout.exe" to spit the contents of an environment variable to stdout just to handle this situation.

 

*   You need to make sure that once you go production it is wise to only use SW_HIDE as the "ExecShow" setting of the INI file.

 

*   If you are using a scripting language such as command shell (cmd.exe) or perl, you need to protect the interpreter from being changed.

 

*   You need to protect anything else you can think of that I've forgotten.

 

*   You need to make sure you understand how all this works!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

The AFSLogonShell Definitions and Function Code

 

 

//------------------------------------------------------------------------------------------------------------

// begin - AFSLogonShell defs and function

//------------------------------------------------------------------------------------------------------------

 

// standard AFSLogonShell Function Mode values

#define define_AFSLogonShell_Mode_FunctionValues  0

#define define_AFSLogonShell_Mode_PassThrough     1

#define define_AFSLogonShell_Mode_SuccessAlways   2

#define define_AFSLogonShell_Mode_SuccessOnly     3

#define define_AFSLogonShell_Mode_UseINIModes     4

 

// standard AFSLogonShell INI Mode values

#define define_AFSLogonShell_INIMode_Undefined      0

#define define_AFSLogonShell_INIMode_PassThrough    1

#define define_AFSLogonShell_INIMode_SuccessAlways  2

#define define_AFSLogonShell_INIMode_SuccessOnly    3

 

// standard AFSLogonShell return values

#define define_AFSLogonShell_Ret_SUCCESS    0

#define define_AFSLogonShell_Ret_FAILURE    1

#define define_AFSLogonShell_Ret_NOENV      2

#define define_AFSLogonShell_Ret_EXECERR    3

#define define_AFSLogonShell_Ret_TIMEOUT    4

#define define_AFSLogonShell_Ret_UNDEFINED  5

 

// Important Notes:

//

// The "dword_Mode" variable controls whether the value of

// the "code" variable that is passed into the function will

// be passed right through regardless of function processing.

// This controls whether the AFSLogonShell function exists in

// the "afslogon.c" code as if it were "invisible".

//

// Using the define "define_AFSLogonShell_Mode_PassThrough"

// for the value of "dword_Mode" means that the "code"

// variable is passed through the function regardless of

// what happens in the function.  In this manner the function

// does not effect normal "afslogon.dll" processing.  The

// function just executes the shell, that's all, all errors

// are ignored.  If the shell didn't execute there's no way

// to know other than a possilby slower execution at runtime.

//

// Using the define "define_AFSLogonShell_Mode_FunctionValues"

// for the value of "dword_Mode" means that the function

// will return the standard AFSLogonShell return values

// based on the function process results.  A programmer

// must add more code in "afslogon.c" to process those

// results after the call is made to prevent interference

// with the regular "afslogon.c" "code" variable values.

//

// Using the define "define_AFSLogonShell_Mode_SuccessAlways"

// for the value of "dword_Mode" means that the function

// returns "define_AFSLogonShell_Ret_SUCCESS" under any

// circumstance.  This will prevent the normal "afslogon.c"

// procedures from producing an error.  You may want to do

// this if you are using the shell for all authentication

// such as in the case when you are using kinit for KRB V.

//

// Using the define "define_AFSLogonShell_Mode_SuccessOnly"

// for the value of "dword_Mode" means that if the function

// successfully executes the child shell process the "code"

// value will be forced to be "define_AFSLogonShell_Ret_SUCCESS".

// This will prevent the "afslogon.c" code from displaying

// an error message from the default authentication routines

// (if it previously occured).  This means that all

// authentication will be handled by the AFSLogonShell child

// process.  If the shell does not execute properly any value

// that "code" currently has will pass right through the

// function.

 

// Using the define "define_AFSLogonShell_Mode_UseINIModes"

// for the value of "dword_Mode" means that the value

// returned will depend on the "Mode" setting from the INI

// file.  In the case where the function cannot find or

// open the INI file and obtain that mode setting, then

// the default mode will be "PassThrough".

 

DWORD dword_AFSLogonShell( HWND hwndOwner, DWORD code, BOOL interactive, BOOL failSilently, DWORD dword_Mode )

{

  // declare some variables

  DWORD               dword_ReturnCode;               // variable holding the function return value

  BOOL                bool_Return;                    // general Win32 function return variable

  DWORD               dword_Return;                   // general Win32 function return variable

  PROCESS_INFORMATION     process_information_Process;    // process information for created process

  STARTUPINFO         startup_info_StartInfo;         // startup information for created process

  CHAR                char_Print[2048];               // string used for formatting dialog box

  CHAR                char_ExecString[256];           // string holding executable process pathname

  CHAR                char_ExecDirectory[256];        // string holding executable process directory

  CHAR                char_ExecTimeOut[256];          // string holding executable process time-out

  CHAR                char_ExecShow[256];             // string holding executable process show mode

  INT                 int_ExecTimeoutCount;           // variable holding time-out count for process (in secs)

  CHAR                *ptr_char_AFSLogonINIVar;       // pointer to AFSLogonINI environment variable contents

  DWORD               dword_ExitCode;                 // variable holding the exit code of the child process

  BOOL                bool_GetExitCodeProcess_Return; // variable holding the GetExitCodeProcess return value

  DWORD               dword_GetLastError;             // variable to hold the Win32 GetLastError value

  CHAR                char_GetLastErrorString[256];   // variable to hold the Win32 GetLastError string

  CHAR                char_INIMode[256];              // string to hold the INI Mode setting

  DWORD               dword_INIMode;                  // variable to hold the value of the INI Mode setting

   

  // init some values

  char_ExecString[0] = 0;

  char_ExecDirectory[0] = 0;

  char_ExecTimeOut[0] = 0;

  char_ExecShow[0] = 0;

  char_INIMode[0] = 0;

  int_ExecTimeoutCount = 0;

  ptr_char_AFSLogonINIVar = NULL;

  dword_GetLastError = 0;

  char_GetLastErrorString[0] = 0;

  dword_ExitCode = 0;

  dword_INIMode = define_AFSLogonShell_INIMode_Undefined;

  dword_ReturnCode = define_AFSLogonShell_Ret_UNDEFINED;

 

  // try to get the AFSLogonINI environment variable contents

  ptr_char_AFSLogonINIVar = getenv("AFSLogonINI");

 

  // check for error

  if ( ptr_char_AFSLogonINIVar == NULL )

  {

       if (interactive && !failSilently)

            MessageBox( hwndOwner, "The \"AFSLogonINI\" environment variable was not found.", "AFS Logon Shell", MB_ICONSTOP | MB_OK );

       dword_ReturnCode = define_AFSLogonShell_Ret_NOENV;

       goto lable_AFSLogonShell_END;

  };

 

  // get the config information from the afslogon.ini file

  GetPrivateProfileString( "Config", "ExecString", "\0", char_ExecString, 255, ptr_char_AFSLogonINIVar );

  GetPrivateProfileString( "Config", "ExecDirectory", "\0", char_ExecDirectory, 255, ptr_char_AFSLogonINIVar );

  GetPrivateProfileString( "Config", "ExecTimeOut", "\0", char_ExecTimeOut, 255, ptr_char_AFSLogonINIVar );

  GetPrivateProfileString( "Config", "ExecShow", "\0", char_ExecShow, 255, ptr_char_AFSLogonINIVar );

  GetPrivateProfileString( "Config", "Mode", "\0", char_INIMode, 255, ptr_char_AFSLogonINIVar );

 

  // convert time-out time to integer

  int_ExecTimeoutCount = atoi( char_ExecTimeOut );

 

  // get INI mode if it exists

  strupr( char_INIMode );

  if ( strcmp( char_INIMode, "PASSTHROUGH" ) == 0 )

       dword_INIMode = define_AFSLogonShell_INIMode_PassThrough;

  else

       if ( strcmp( char_INIMode, "SUCCESSALWAYS" ) == 0 )

            dword_INIMode = define_AFSLogonShell_INIMode_SuccessAlways;

       else

            if ( strcmp( char_INIMode, "SUCCESSONLY" ) == 0 )

                dword_INIMode = define_AFSLogonShell_INIMode_SuccessOnly;

       

  // clean structure

  ZeroMemory( &process_information_Process, sizeof(PROCESS_INFORMATION) );

 

  // setup startup information

  GetStartupInfo( &startup_info_StartInfo );

  startup_info_StartInfo.dwFlags = STARTF_USESHOWWINDOW;

  strupr( char_ExecShow );

  if ( strcmp( char_ExecShow, "SW_SHOW" ) == 0 )

       startup_info_StartInfo.wShowWindow = SW_SHOW;

  else

       startup_info_StartInfo.wShowWindow = SW_HIDE;

 

  // execute child shell process

  bool_Return = CreateProcess

  (

       NULL,

       char_ExecString,

       NULL,

       NULL,

       0x01,

       NORMAL_PRIORITY_CLASS,

       NULL,

       char_ExecDirectory,

       &startup_info_StartInfo,

       &process_information_Process

  );

 

  // check if started ok

  if ( bool_Return != TRUE )

  {

       if (interactive && !failSilently)

       {

            dword_GetLastError = GetLastError();

            strcpy( char_GetLastErrorString, "Unknown" );

            dword_Return = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, dword_GetLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), char_GetLastErrorString, 255, NULL );

            sprintf( char_Print, "Unable to start the AFS logon shell process.\n\nExecString=\"%s\"\n\nWin32 error = %d, %s", char_ExecString, dword_GetLastError, char_GetLastErrorString );

            MessageBox( hwndOwner, char_Print, "AFS Logon Shell", MB_ICONSTOP | MB_OK );

       }

       dword_ReturnCode = define_AFSLogonShell_Ret_EXECERR;

       goto lable_AFSLogonShell_END;

  }

 

  // wait on child execution to complete

  do

  {

       dword_Return = WaitForSingleObject( process_information_Process.hProcess, 1000 );

       int_ExecTimeoutCount--;

  } while ( dword_Return == WAIT_TIMEOUT && int_ExecTimeoutCount > 0 );

 

  // get termination code

  bool_GetExitCodeProcess_Return = GetExitCodeProcess( process_information_Process.hProcess, &dword_ExitCode );

 

  // now close the handle(s)

  CloseHandle( process_information_Process.hProcess );

  CloseHandle( process_information_Process.hThread );

 

  // did the process time-out?

  if ( dword_Return == WAIT_TIMEOUT )

  {

       if (interactive && !failSilently)

            MessageBox( hwndOwner, "A time-out occured waiting for execution of the AFS logon shell process to complete.", "AFS Logon Shell", MB_ICONSTOP | MB_OK );

       dword_ReturnCode = define_AFSLogonShell_Ret_TIMEOUT;

       goto lable_AFSLogonShell_END;

  }

 

  // process termination code

  if ( dword_ExitCode != 0 || bool_GetExitCodeProcess_Return == 0 )

  {

       dword_ReturnCode = define_AFSLogonShell_Ret_FAILURE;

       goto lable_AFSLogonShell_END;

  }

 

  dword_ReturnCode = define_AFSLogonShell_Ret_SUCCESS;

 

lable_AFSLogonShell_END:

 

  // INI return modes

  switch ( dword_INIMode )

  {

       case define_AFSLogonShell_INIMode_SuccessAlways:

            dword_Mode = define_AFSLogonShell_Mode_SuccessAlways;

            break;

 

       case define_AFSLogonShell_INIMode_SuccessOnly:

            dword_Mode = define_AFSLogonShell_Mode_SuccessOnly;

            break;

 

       case define_AFSLogonShell_INIMode_Undefined:

            if ( dword_Mode == define_AFSLogonShell_Mode_UseINIModes )

                 dword_Mode = define_AFSLogonShell_Mode_PassThrough;

            break;

 

       default:

            dword_Mode = define_AFSLogonShell_Mode_PassThrough;

  }

 

  // function return modes

  switch ( dword_Mode )

  {

       case define_AFSLogonShell_Mode_FunctionValues:

            return dword_ReturnCode;

 

       case define_AFSLogonShell_Mode_SuccessAlways:

            return define_AFSLogonShell_Ret_SUCCESS;

 

       case define_AFSLogonShell_Mode_SuccessOnly:

            if ( dword_ReturnCode == define_AFSLogonShell_Ret_SUCCESS )

                 return define_AFSLogonShell_Ret_SUCCESS;

            else

                 return code;

 

       default:

            return code;

  }

}

 

//------------------------------------------------------------------------------------------------------------

// end - AFSLogonShell defs and function

//------------------------------------------------------------------------------------------------------------

 

 

 

 

 

 

 

 

 

The AFSLogonShell Function Call

 

 

//------------------------------------------------------------------------

// call AFSLogonShell

//------------------------------------------------------------------------

code = dword_AFSLogonShell( hwndOwner, code, interactive, failSilently, define_AFSLogonShell_Mode_UseINIModes );