home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / netds / sna / aremote / arsetup.c < prev    next >
Text File  |  1997-04-09  |  41KB  |  1,152 lines

  1. /*************************************************************************\
  2. *
  3. * APPC Remote Installer
  4. *
  5. * This program brings up a dialog box that prompts for TP configuration
  6. * information.  The information is then placed in the registry under
  7. * Windows NT, and in the WIN.INI file under Windows.  The WIN32 compiler
  8. * flag specifies the NT version, while the WINDOWS flag specifies the
  9. * Windows version.
  10. *
  11. * 6/93 Initial coding    ARK
  12. * 6/94 Modified to work with APPC Remote    T-ALEXWE
  13. *
  14. \*************************************************************************/
  15.  
  16. #define STRICT
  17. #include <windows.h>
  18. #ifdef WIN32
  19. #include <winsvc.h>
  20. #endif
  21.  
  22. #ifdef        WIN32
  23.         #include <windowsx.h>
  24. #else
  25.         #include <windowsx.h16>
  26. #endif
  27.  
  28. #include <string.h>
  29. #include <stdlib.h>
  30. #include <stdio.h>
  31. #include <stdarg.h>
  32. #include "arsetup.h"
  33.  
  34. HANDLE  hInst;                       // This program's instance
  35. HWND    hDialog;                     // Global handle to main dialog
  36. HWND    hTimeout;                    // A handle to the "timeout" window
  37. HWND    hList;                       // Handle to the "users" list box
  38.  
  39. WNDPROC lPrevWndProcInt = NULL,      // Holds original integer edit box window proc
  40.         lPrevWndProcAppc = NULL,     // Holds original APPC edit box window proc
  41.         lPrevWndProcInfinite = NULL, // Original window proc for "infinite"
  42.                                      // radio button
  43.         lPrevWndProcConvSec = NULL,  // Same for "conversation security" box
  44.         lPrevWndProcService = NULL;  // Same for "service" box
  45.  
  46. FARPROC lpfnInt = NULL,              // Pointer to the ValidateFieldInt
  47.                                      // procedure; becomes non-NULL upon
  48.                                      // initialization in ValidateField().
  49.         lpfnAppc = NULL;         // Pointer to ValidateFieldAppc procedure
  50.  
  51. /*************************************************************************\
  52. *
  53. *  FUNCTION: WinMain(HANDLE, HANDLE, LPSTR, int)
  54. *
  55. *  PURPOSE: Creates the dialog box.
  56. *
  57. *  COMMENTS:
  58. *
  59. \*************************************************************************/
  60.  
  61. int PASCAL WinMain (HINSTANCE hInstance,
  62.                       HINSTANCE hPrevInstance,
  63.                       LPSTR     lpCmdLine,
  64.                       int       nCmdShow)
  65. {
  66.   DWORD retCode;
  67.   FARPROC lpfn;
  68.  
  69.   hInst   = hInstance;
  70.  
  71.   lpfn = (FARPROC) MakeProcInstance( (FARPROC)MainDlgProc, hInst);
  72.   retCode = DialogBoxParam ((HANDLE)hInst,
  73.                             (LPCSTR)"MainDlg",
  74.                             NULL,
  75.                             (DLGPROC) lpfn,
  76.                             0);
  77.   FreeProcInstance(lpfn);
  78.  
  79.   return  (retCode);
  80. }
  81. /************************************************************************/
  82. /*
  83.  *  MainDlgProc: Handle messages to the main dialog.
  84.  *  Note:  Under Windows NT, installation consists of creating a service
  85.  *         and creating some keys in the registry, while under Windows
  86.  *         we instead add slightly different information to the WIN.INI file.
  87.  *         So two different dialogs are necessary for the different operating
  88.  *         systems.  Appropriate sections of the procedure below are
  89.  *         #ifdef'ed to handle parts of the installation that are unique
  90.  *         to a particular operating system.
  91.  */
  92. /************************************************************************/
  93. BOOL CALLBACK MainDlgProc (HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
  94.   {
  95.   static   HWND hInfinite;
  96.   char     lpPathName[MAXBINPATHLEN], *lpTPName, lpParameters[MAXBINPATHLEN];
  97.   char     *lpTimeout, *lpLocalLUName, *lpCommand, *divider;
  98.   char     lpFileTitle[MAXBINPATHLEN], lpSysDir[MAXBINPATHLEN];
  99.   INT      cmd;
  100.   char     lpszName[MAX_COMPUTERNAME_LENGTH + 1];
  101.   int      lpcchName = MAX_COMPUTERNAME_LENGTH + 1;
  102.   HANDLE   hTemp;
  103.   WIN32_FIND_DATA  fdFile;
  104.                                                                 
  105. #ifdef WIN32
  106.   UNREFERENCED_PARAMETER(divider);
  107.   UNREFERENCED_PARAMETER(lpParameters);
  108. #endif
  109.  
  110.   switch (wMsg)
  111.     {
  112.       case WM_INITDIALOG:
  113.         hDialog = hDlg;
  114.  
  115.         // Set maximum TP name to 128 characters
  116.         SendMessage(GetDlgItem(hDlg, IDE_TPNAME),
  117.                     EM_LIMITTEXT, MAXTPLEN, 0);
  118.  
  119.         // set the default TPName
  120.         SetDlgItemText(hDlg, IDE_TPNAME, "AREMOTE");
  121.  
  122.         // Set maximum pathname to 512 characters
  123.         SendMessage(GetDlgItem(hDlg, IDE_CMDPATH),
  124.                     EM_LIMITTEXT, MAXBINPATHLEN, 0);
  125.  
  126.         // Have it use cmd.exe by default for the command
  127.         GetSystemDirectory(lpSysDir, sizeof(lpSysDir));
  128.         strcat(lpSysDir, "\\cmd.exe");
  129.         SetDlgItemText(hDlg, IDE_CMDPATH, lpSysDir);
  130.  
  131.         // Set maximum pathname to 512 characters
  132.         SendMessage(GetDlgItem(hDlg, IDE_REMOTEPATH),
  133.                     EM_LIMITTEXT, MAXBINPATHLEN, 0);
  134.  
  135.         // Have it use aremote.exe by default for remote
  136.         GetCurrentDirectory(sizeof(lpSysDir), lpSysDir);
  137.         strcat(lpSysDir, "\\aremote.exe");
  138.         SetDlgItemText(hDlg, IDE_REMOTEPATH, lpSysDir);
  139.  
  140.         // Allow only legal characters in "Local LU Alias" box:
  141.         ValidateField(GetDlgItem(hDlg, IDE_LOCALLU), VALIDATE_APPC);
  142.  
  143.         // Set maximum length of LU name to be 8 characters
  144.         SendMessage(GetDlgItem(hDlg, IDE_LOCALLU), EM_LIMITTEXT, MAXLULEN, 0);
  145.  
  146.         // set the LU name to this computers name by default
  147.         GetComputerName(lpszName, &lpcchName);
  148.         MakeValidLUName(lpszName);
  149.         SetDlgItemText(hDlg, IDE_LOCALLU, lpszName);
  150.  
  151.         return TRUE;
  152.  
  153.       case WM_COMMAND:
  154.         switch (cmd = GET_WM_COMMAND_ID(wParam, lParam))
  155.           {
  156.             case IDOK:
  157.               // User hits OK; we get relevant info & try to install.
  158.  
  159.               // If TP name field is blank, error out
  160.               if (ReadString(hDlg, IDE_TPNAME, &lpTPName, MAXTPLEN) == 0)
  161.                 {
  162.                   DisplayError(hDlg, IDS_BADTPNAME);
  163.                   free(lpTPName);
  164.                   return TRUE;
  165.                 }
  166.  
  167.               // read path name for remote
  168.               ReadString(hDlg, IDE_REMOTEPATH, &lpCommand, MAXBINPATHLEN);
  169.               
  170.               // open the file to make sure it exists
  171.               hTemp = FindFirstFile(lpCommand, &fdFile);
  172.               if (hTemp == INVALID_HANDLE_VALUE) {
  173.                   DisplayError(hDlg, IDS_BADREMOTEPATH);
  174.                   return TRUE;
  175.               } else CloseHandle(hTemp);
  176.                 
  177.               strcpy(lpPathName, lpCommand);
  178.               strcat(lpPathName, " /a ");
  179.  
  180.               // read path name for command
  181.               ReadString(hDlg, IDE_CMDPATH, &lpCommand, MAXBINPATHLEN);
  182.  
  183.               // open the file to make sure it exists
  184.               hTemp = FindFirstFile(lpCommand, &fdFile);
  185.               if (hTemp == INVALID_HANDLE_VALUE) {
  186.                   DisplayError(hDlg, IDS_BADCMDPATH);
  187.                   return TRUE;
  188.               } else CloseHandle(hTemp);
  189.                 
  190.               strcat(lpPathName, lpCommand);
  191.  
  192.               strcpy(lpParameters, "/a ");
  193.               strcat(lpParameters, lpCommand);
  194.  
  195.               // read Local LU Alias
  196.               ReadString(hDlg, IDE_LOCALLU, &lpLocalLUName, MAXLULEN);
  197.               if (strlen(lpLocalLUName) < 1) {
  198.                   DisplayError(hDlg, IDS_BADLUNAME);
  199.                   return TRUE;
  200.               }
  201.  
  202.               lpTimeout = INFINITE_TIMEOUT;
  203.  
  204. #ifdef WIN32
  205.               // Install the new service; if successful, set subkeys
  206.               if (InstallServiceNT(hDlg, lpTPName, lpPathName) != 0)
  207.                 {
  208.                   free(lpPathName);
  209.                   free(lpTPName);
  210.                   return TRUE;
  211.                 }
  212.  
  213.               if (CreateKeys(  lpTPName,
  214.                                TRUE,
  215.                                lpLocalLUName,
  216.                                FALSE,
  217.                                FALSE,
  218.                                FALSE,
  219.                                lpTimeout,
  220.                                lpParameters,
  221.                                lpPathName
  222.                                )
  223.                                != 0)
  224.                 {
  225.                   free(lpPathName);
  226.                   free(lpTPName);
  227.                   return TRUE;
  228.                 }
  229.               DisplayInfo(hDlg, IDS_SUCCESS);
  230.  
  231. #else
  232.  
  233.               // Add entries to WIN.INI
  234.               if (InstallWindows(hDlg, lpTPName, lpPathName,
  235.                      lpLocalLUName, lpParameters,
  236.                                  IsDlgButtonChecked(hDlg, IDC_CONVSEC) == 1,
  237.                                  IsDlgButtonChecked(hDlg, IDC_ALREADYVER) == 1,
  238.                                  IsDlgButtonChecked(hDlg, IDC_QUEUED) == 1,
  239.                                  lpTimeout
  240.                                  ) != 0)
  241.                 {
  242.                   free(lpPathName);
  243.                   free(lpTPName);
  244.                   return TRUE;
  245.                 }
  246.               DisplayInfo(hDlg, IDS_SUCCESS);
  247.  
  248. #endif //ifdef WIN32
  249.  
  250.               EndDialog(hDlg, TRUE);
  251.               free(lpPathName);
  252.               free(lpTPName);
  253.               return TRUE;
  254.  
  255.             case IDC_CMDPATH_BROWSE: {
  256.                 char filters[256];
  257.                 char lpDirPath[MAXBINPATHLEN], lpFileName[MAXBINPATHLEN];
  258.                 char *lpTemp;
  259.                 OPENFILENAME ofn;
  260.  
  261.                 GetDlgItemText(hDlg, IDE_CMDPATH, lpFileName, 
  262.                     sizeof(lpFileName));
  263.                 GetFullPathName(lpFileName, sizeof(lpDirPath), 
  264.                     lpDirPath, &lpTemp);
  265.                 strcpy(lpFileName, lpTemp);
  266.                 lpTemp[0] = 0;
  267.  
  268.                 memcpy(filters, 
  269.                     "Executable Programs\0*.exe;*.bat;*.com\0\0\0", 256);
  270.  
  271.                 memset(&ofn, 0, sizeof(ofn));
  272.                 ofn.lStructSize = sizeof(OPENFILENAME);
  273.                 ofn.hwndOwner = hDlg;
  274.                 ofn.lpstrFilter = filters;
  275.                 ofn.lpstrCustomFilter = NULL;
  276.                 ofn.nFilterIndex = 0;
  277.                 ofn.lpstrFile = lpFileName;
  278.                 ofn.nMaxFile = sizeof(lpFileName);
  279.                 ofn.lpstrFileTitle = lpFileTitle;
  280.                 ofn.nMaxFileTitle = sizeof(lpFileTitle);
  281.                 ofn.lpstrTitle = "Path to Command";
  282.                 ofn.lpstrInitialDir = lpDirPath;
  283.                 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST |
  284.                             OFN_HIDEREADONLY;
  285.  
  286.                 if (GetOpenFileName(&ofn)) {
  287.                     SetDlgItemText(hDlg, IDE_CMDPATH, lpFileName);
  288.                 }
  289.  
  290.                 return TRUE;
  291.               }
  292.  
  293.             case IDC_REMOTEPATH_BROWSE: {
  294.                 char filters[256];
  295.                 char lpDirPath[MAXBINPATHLEN], lpFileName[MAXBINPATHLEN];
  296.                 char *lpTemp;
  297.                 OPENFILENAME ofn;
  298.  
  299.                 GetDlgItemText(hDlg, IDE_REMOTEPATH, lpFileName, 
  300.                     sizeof(lpFileName));
  301.                 GetFullPathName(lpFileName, sizeof(lpDirPath), 
  302.                     lpDirPath, &lpTemp);
  303.                 strcpy(lpFileName, lpTemp);
  304.                 lpTemp[0] = 0;
  305.  
  306.                 memcpy(filters, 
  307.                     "APPC Remote\0aremote.exe\0Executable Programs\0*.exe;*.bat;*.com\0\0\0", 
  308.                     256);
  309.  
  310.                 memset(&ofn, 0, sizeof(ofn));
  311.                 ofn.lStructSize = sizeof(OPENFILENAME);
  312.                 ofn.hwndOwner = hDlg;
  313.                 ofn.lpstrFilter = filters;
  314.                 ofn.lpstrCustomFilter = NULL;
  315.                 ofn.nFilterIndex = 0;
  316.                 ofn.lpstrFile = lpFileName;
  317.                 ofn.nMaxFile = sizeof(lpFileName);
  318.                 ofn.lpstrFileTitle = lpFileTitle;
  319.                 ofn.nMaxFileTitle = sizeof(lpFileTitle);
  320.                 ofn.lpstrTitle = "Path to APPC Remote";
  321.                 ofn.lpstrInitialDir = lpDirPath;
  322.                 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST |
  323.                             OFN_HIDEREADONLY;
  324.  
  325.                 if (GetOpenFileName(&ofn)) {
  326.                     SetDlgItemText(hDlg, IDE_REMOTEPATH, lpFileName);
  327.                 }
  328.  
  329.                 return TRUE;
  330.               }
  331.  
  332.             case IDCANCEL:
  333.               EndDialog(hDlg, FALSE);
  334.               return TRUE;
  335.           }
  336.  
  337.         break;
  338.  
  339.       case WM_DESTROY:
  340.         PostQuitMessage(0);
  341.         return TRUE;
  342.     }
  343.     return FALSE;
  344. }
  345.  
  346.  
  347. #ifdef WIN32
  348.  
  349. /*****************************************************************************/
  350. /*
  351.  * InstallServiceNT( HWND hDlg, LPSTR lpServiceName, LPSTR lpPath )
  352.  *
  353.  * Windows NT version of installation--register a new service.
  354.  *
  355.  * Parameters
  356.  * ----------
  357.  *
  358.  * hDlg: Handle to top level dialog; used for error messages.
  359.  * lpServiceName: A string giving the name of the TP.
  360.  * lpPath: A string giving the full pathname of the TP's executable.
  361.  *
  362.  * Returns
  363.  * -------
  364.  *
  365.  *  Zero upon successful completion, nonzero otherwise.
  366.  *
  367.  * Comments
  368.  * --------
  369.  *  Installs new service if possible.  Puts up appropriate message boxes if
  370.  *  installation fails.
  371.  */
  372. /*****************************************************************************/
  373. int InstallServiceNT(HWND hDlg, LPSTR lpServiceName, LPSTR lpBinaryPath)
  374. {
  375.  
  376.   SC_HANDLE hSCManager = NULL;
  377.   SC_HANDLE hService   = NULL;
  378.   SC_LOCK   lSCLock    = NULL;
  379.  
  380.   hSCManager = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
  381.  
  382.   if( hSCManager != NULL )
  383.   {
  384.     /*************************************************************************/
  385.     /* Lock the service database                                             */
  386.     /*************************************************************************/
  387.     lSCLock = LockServiceDatabase( hSCManager );
  388.     if ( lSCLock != NULL )
  389.     {
  390.       /***********************************************************************/
  391.       /* Create the service                                                  */
  392.       /***********************************************************************/
  393.       hService = CreateService( hSCManager,
  394.                                 lpServiceName,     // Service's name
  395.                                 lpServiceName,     // Display name (new for NT)
  396.                                 SERVICE_ALL_ACCESS,// Access (allow all)
  397.                                 0x10,              // Service type
  398.                                 0x3,               // Startup behavior
  399.                                 0x1,               // Error control
  400.                                 lpBinaryPath,      // Full pathname of binary
  401.                                 NULL,              // Load order group
  402.                                 NULL,              // Tag ID
  403.                                 NULL,              // Dependencies (none)
  404.                                 NULL,              // Account name
  405.                                 NULL               // Password
  406.                                 );
  407.       if ( hService != NULL )
  408.         {
  409.           /*********************************************************************/
  410.           /* Close our handle to the new service                               */
  411.           /*********************************************************************/
  412.           CloseServiceHandle( hService );
  413.         }
  414.       else
  415.         {
  416.           // Try to display the reason for the create failure
  417.           ParseCreateError(hDlg, GetLastError());
  418.  
  419.           // Must release lock in case we are called again
  420.           UnlockServiceDatabase( lSCLock );
  421.  
  422.           return 1;
  423.         }
  424.  
  425.       /***********************************************************************/
  426.       /* Unlock the database                                                 */
  427.       /***********************************************************************/
  428.       UnlockServiceDatabase( lSCLock );
  429.     }
  430.     else
  431.       {
  432.         DisplayError(hDlg, IDS_LOCKFAILED);
  433.         return 1;
  434.       }
  435.     /*************************************************************************/
  436.     /* Free our handle to the service control manager                        */
  437.     /*************************************************************************/
  438.     CloseServiceHandle( hSCManager );
  439.     return 0;
  440.   }
  441.  
  442.   DisplayError(hDlg, IDS_OPENSCMFAILED);
  443.   return 1;
  444. }
  445.  
  446. /*****************************************************************************/
  447. /*
  448.  * CreateKeys: Set the required key values for the newly created service.
  449.  *  The keys are:
  450.  *                 Linkage:
  451.  *                        OtherDependencies: REG_MULTI_SZ: SnaBase
  452.  *                Parameters:
  453.  *                        SNAServiceType: REG_DWORD: 0x5
  454.  *                        ConversationSecurity: REG_SZ:  "yes" or "no"
  455.  *                           (depending on setting of radio button in dialog box)
  456.  *                AlreadyVerified: REG_SZ: "yes" or "no"
  457.  * If the timeout isn't infinite, there appears the key:
  458.  *                        Timeout: REG_DWORD: <timeout in seconds>
  459.  *                        Parameters: REG_SZ: cmd line parameters
  460.  * If ConversationSecurity is yes, there are also these keys:
  461.  *                        <User1>: REG_SZ: <Password1>
  462.  *                                ...
  463.  *                        <Usern>: REG_SZ: <Passwordn>
  464.  *
  465.  *  Note that the users and passwords are read from the list box.
  466.  *
  467.  *  Arguments:
  468.  *    lpServiceName is the name of the TP.
  469.  *    lpLUName is the Local LU Alias.
  470.  *    The two integer arguments give the state of the radio buttons on the
  471.  *      dialog box.  They should be zero to indicate that "No" is selected,
  472.  *      nonzero otherwise.
  473.  *    lpTimeout is a string containing the timeout value; if the timeout is
  474.  *      infinite, it holds the value of INFINITE_TIMEOUT.
  475.  *
  476.  *  Returns: 0 on success, nonzero otherwise.
  477.  */
  478. /*****************************************************************************/
  479. INT CreateKeys(LPSTR lpServiceName, BOOL bService, LPSTR lpLUName, int iConvSec,
  480.                int iAlreadyVer, int iQueued, LPSTR lpTimeout, LPSTR lpParameters,
  481.                LPSTR lpExeName)
  482. {
  483.   LPSTR  lpServiceFullName;
  484.   int    bufsize, i, nCount;
  485.   LOCALHANDLE hLocalMem;
  486.  
  487.   // If any of the buttons says "no", change the key values so that they
  488.   // will be inserted into the registry below.  This is kind of gross, but
  489.   // it keeps the loop below simple.
  490.   if (iConvSec == 0)
  491.     {
  492.       keyinfo[CONVSEC].lpData = "no";
  493.       keyinfo[CONVSEC].iDataSize = 3;
  494.     }
  495.   if (iAlreadyVer == 0)
  496.     {
  497.       keyinfo[ALREADYVER].lpData = "no";
  498.       keyinfo[ALREADYVER].iDataSize = 3;
  499.     }
  500.   if (bService == FALSE && iQueued == 0)
  501.     {
  502.       //
  503.       // the default of 5 is OK for queued support.
  504.       // needs to be redefined to 6 for nonqueued.
  505.       //
  506.       keyinfo[SNASRVTYPE].lpData = "6";
  507.       keyinfo[SNASRVTYPE].iDataSize = 4;
  508.     }
  509.  
  510.   // Put timeout in structure (it won't be written out if infinite)
  511.   keyinfo[TIMEOUT].lpData = lpTimeout;
  512.  
  513.   // Put Parameters in structure
  514.   keyinfo[PARAMETERS].lpData = lpParameters;
  515.   keyinfo[PARAMETERS].iDataSize = strlen(lpParameters) + 1;   // Count null
  516.  
  517.   // Put Local LU Alias in structure; it won't be written out if blank
  518.   keyinfo[LUNAME].lpData = lpLUName;
  519.   keyinfo[LUNAME].iDataSize = strlen(lpLUName) + 1;   // Count null
  520.  
  521.   // Create full path of TP by concatenating the registry path with the
  522.   // TP's name
  523.   bufsize = MAXREGPATHLEN + 1;
  524.   lpServiceFullName = (LPSTR) malloc(bufsize);
  525.  
  526.   i = bService ? IDS_REGISTRYPATH : IDS_REGISTRYAPPLPATH ;
  527.   LoadString( hInst, i, lpServiceFullName, bufsize );
  528.   lstrcat(lpServiceFullName, lpServiceName);
  529.  
  530.   // Write out the basic keys; first key is skipped for non-service tps
  531.   for ( i = bService ? 0 : 1; i < NUMKEYS; i++ )
  532.     {
  533.       if ( WriteKeyNT(lpServiceFullName, keyinfo[i], TRUE ) )
  534.           return 1;
  535.     }
  536.  
  537.   // if not a service then write out the path name
  538.   if ( bService == FALSE )
  539.     {
  540.       keyinfo[EXENAME].lpData = lpExeName;
  541.       keyinfo[EXENAME].iDataSize = strlen(lpExeName) + 1;   // Count null
  542.       if (WriteKeyNT(lpServiceFullName, keyinfo[EXENAME], TRUE ) )
  543.           return 1;
  544.     }
  545.  
  546.   // If timeout isn't infinite, write out the "timeout" key
  547.   if (lstrcmp(lpTimeout, INFINITE_TIMEOUT))
  548.     {
  549.       if (WriteKeyNT(lpServiceFullName, keyinfo[TIMEOUT], TRUE ) )
  550.           return 1;
  551.     }
  552.  
  553.   // If Local LU Alias isn't blank, write out its key
  554.   if (strlen(lpLUName) > 0)
  555.      {
  556.       if (WriteKeyNT(lpServiceFullName, keyinfo[LUNAME], TRUE ) )
  557.           return 1;
  558.     }
  559.  
  560.   // Only write out user stuff if conversation security is "yes"
  561.   if (iConvSec)
  562.     {
  563.       keyinfo[USER].lpName = (LPSTR) malloc(MAXUSERNAMELEN + 1);
  564.       nCount = SendMessage(hList, LB_GETCOUNT, 0, 0);
  565.       for (i = 0; i < nCount; i++)
  566.         {
  567.           // Get username & make it the name of this key
  568.           SendMessage(hList, LB_GETTEXT, (WPARAM) i,
  569.                                   (LPARAM) keyinfo[USER].lpName);
  570.  
  571.           // Get pointer to password and then get password
  572.           hLocalMem = (LOCALHANDLE) SendMessage(hList, LB_GETITEMDATA, i, 0);
  573.           keyinfo[USER].lpData = (LPSTR) LocalLock(hLocalMem);
  574.  
  575.           // Password shouldn't be blank, but just in case...
  576.           if (keyinfo[USER].lpData == NULL)
  577.             keyinfo[USER].iDataSize = 0;
  578.           else
  579.             keyinfo[USER].iDataSize = strlen(keyinfo[USER].lpData);
  580.  
  581.           if (WriteKeyNT(lpServiceFullName, keyinfo[USER], TRUE ) )
  582.             {
  583.               LocalUnlock(hLocalMem);
  584.               return 1;
  585.             }
  586.           LocalUnlock(hLocalMem);
  587.         }
  588.     }
  589.  
  590.   free(lpServiceFullName);
  591.   return 0;
  592. }
  593. /*****************************************************************************/
  594. /*
  595.  * WriteKeyNT: Write out the given key to the registry.
  596.  * Return 0 on success, nonzero otherwise.
  597.  */
  598. /*****************************************************************************/
  599. INT WriteKeyNT(LPSTR lpServiceFullName, KEYENTRY keyinfo, BOOL bAppendParent )
  600. {
  601. static BOOL bAskToReplace = TRUE;
  602.   DWORD     dwResult;
  603.   HKEY      hKey;
  604.   LPSTR     lpKeyName;
  605.   DWORD     dwTemp;
  606.  
  607.   lpKeyName = (LPSTR) malloc(MAXREGPATHLEN + 1);
  608.  
  609.   // Build full pathname of key by appending a backslash and the name of the
  610.   // key to the full pathname of the TP.
  611.   lstrcpy(lpKeyName, lpServiceFullName);
  612.   lstrcat(lpKeyName, TEXT("\\"));
  613.   lstrcat(lpKeyName, keyinfo.lpParent);
  614.  
  615.   // Now create the subkeys and set their values
  616.   if (RegCreateKeyEx(HKEY_LOCAL_MACHINE,      // Predefined key
  617.       lpKeyName,                      // Our subkey's name
  618.       0,                       // Reserved
  619.       NULL,                    // Class string
  620.       REG_OPTION_NON_VOLATILE, // Option type (value is saved)
  621.       KEY_ALL_ACCESS,          // Access mask
  622.       NULL,                    // Security attributes
  623.       &hKey,                   // Handle to key is put here
  624.       &dwResult)               // Was key opened or created?
  625.       == ERROR_SUCCESS)
  626.         {
  627.           if ( bAskToReplace && dwResult == REG_OPENED_EXISTING_KEY )
  628.             {
  629.             char szCaption[64];
  630.             char szText[256];
  631.               LoadString( hInst, IDS_REPLACECAPTION, szCaption, sizeof(szCaption) );
  632.               LoadString( hInst, IDS_REPLACETEXT, szText, sizeof(szText) );
  633.               switch( MessageBox( hDialog, szText, szCaption, MB_OKCANCEL|MB_ICONQUESTION ) )
  634.                 {
  635.               case IDCANCEL:
  636.                   return 1;
  637.  
  638.                 }
  639.             }
  640.  
  641.           //
  642.           // after first successful pass no need to query
  643.           //
  644.           bAskToReplace = FALSE;
  645.  
  646.           // We stored all the values as strings, but some are actually numbers.
  647.           // So we have to make a temporary buffer to pass RegSetValueEx the
  648.           // pointer to a DWORD that it wants for DWORD data.
  649.           if ( keyinfo.iDataType == REG_DWORD )
  650.             {
  651.               dwTemp = atol(keyinfo.lpData);
  652.               keyinfo.lpData = (LPSTR) &dwTemp;
  653.               keyinfo.iDataSize = sizeof(DWORD);
  654.             }
  655.  
  656.           if (RegSetValueEx(hKey,
  657.                         keyinfo.lpName,    // Key name
  658.                         0,                    // Reserved
  659.                         keyinfo.iDataType, // Type of key data
  660.                         keyinfo.lpData,    // Key's value
  661.                         keyinfo.iDataSize  // Length of value (incl. nulls)
  662.                         )
  663.                         != ERROR_SUCCESS)
  664.             {
  665.               DisplayError(hDialog, IDS_SETKEYFAILED);
  666.               return 1;
  667.             }
  668.         }
  669.       else
  670.         {
  671.           DisplayError(hDialog, IDS_OPENKEYFAILED);
  672.           return 1;
  673.         }
  674.  
  675.   free(lpKeyName);
  676.   return 0;
  677. }
  678. /*****************************************************************************/
  679. /*
  680.  * ParseCreateError: Given an error code from a registry operation, attempt
  681.  *   to give an appropriate error message.
  682.  */
  683. /*****************************************************************************/
  684. void ParseCreateError(HWND hDlg, UINT uError)
  685. {
  686.   UINT code;
  687.   switch (uError)
  688.     {
  689.       case ERROR_INVALID_PARAMETER:
  690.         // "Invalid parameter" is a rather generic error name.  Since a blank
  691.         // service name is the most probable way the error arose, say so:
  692.         code = IDS_BADPATHNAME;
  693.         break;
  694.  
  695.       case ERROR_INVALID_NAME:
  696.         code = IDS_BADTPNAME;
  697.         break;
  698.  
  699.       case ERROR_SERVICE_EXISTS:
  700.         code = IDS_SERVICEEXISTS;
  701.         break;
  702.  
  703.       default:
  704.         // Since we don't have a code for this error, the following will bring
  705.         // up the "unknown" error message
  706.         code = uError;
  707.     }
  708.   DisplayError(hDlg, code);
  709. }
  710.  
  711. #else
  712.  
  713. /*****************************************************************************/
  714. /*
  715.  * InstallWindows: Windows version of installation; add lines to WIN.INI
  716.  *
  717.  * First we must add an entry under the heading SNAServerAutoTPs.  This entry
  718.  * has the name of the TP and points to another heading that contains TP-
  719.  * specific data.  Sample WIN.INI:
  720.  *
  721.  * [SNAServerAutoTPs]
  722.  * BounceTP = BounceTPParams
  723.  *
  724.  * [BounceTPParams]
  725.  * PathName = c:\sna\bounce.exe
  726.  * Parameters = /t
  727.  * etc....
  728.  *
  729.  * For a list of entries, see the comment at CreateKeys().  The only difference
  730.  * is that WIN.INI also contains an entry for the "queued" toggle.
  731.  *
  732.  *
  733.  * Parameters:
  734.  *    szTPName: The name of the TP
  735.  *    szBinaryPath: The full pathname of the executable
  736.  *    szLUName: Local LU Alias
  737.  *    szParameters: The list of command line parameters
  738.  *    iConvSec: "Conversation security" toggle (1 = yes, 0 = no)
  739.  *    iAlreadyVer: "Already verified" toggle
  740.  *    iQueued: "Queued" toggle
  741.  *    lpTimeout: A string specifying the timeout in seconds, e.g. "2"
  742.  *
  743.  * Returns: zero on success, nonzero otherwise
  744.  */
  745. /*****************************************************************************/
  746. INT InstallWindows(HWND hDlg, char *szTPName, char *szBinaryPath, char *szLUName,
  747.                    char *szParameters,
  748.                    int iConvSec, int iAlreadyVer, int iQueued, char *lpTimeout
  749.                    )
  750. {
  751.   char szNewKeyName[MAXTPLEN + 6];
  752.   int  i, nCount;
  753.   LOCALHANDLE hLocalMem;
  754.  
  755.   // Make new heading by concatenating TP's name and the string "Params"
  756.   strcpy(szNewKeyName, szTPName);
  757.   strcat(szNewKeyName, "Params");
  758.  
  759.   // Now add the key to the SNAServerAutoTPs heading
  760.   if (WriteKeyWindows("SNAServerAutoTPs", szTPName, szNewKeyName))
  761.     return 1;
  762.  
  763.   // Set the data fields of the keys, one at a time
  764.   keyinfo[PATHNAME].data    = szBinaryPath;
  765.   keyinfo[PARAMETERS].data  = szParameters;
  766.   keyinfo[LUNAME].data      = szLUName;
  767.   keyinfo[QUEUED].data      = iQueued ? "yes" : "no";
  768.   keyinfo[TIMEOUT].data     = lpTimeout;
  769.   keyinfo[CONVSEC].data     = iConvSec ? "yes" : "no";
  770.   keyinfo[ALREADYVER].data  = iAlreadyVer ? "yes" : "no";
  771.  
  772.   // Loop through all the keys and add them under the new "Params" heading
  773.   for (i=0; i < NUMKEYS; i++)
  774.     if (WriteKeyWindows(szNewKeyName, keyinfo[i].name, keyinfo[i].data))
  775.       return 1;
  776.  
  777.   // Only write out Local LU Alias if it's not blank
  778.   if (strlen(szLUName) > 0)
  779.     if (WriteKeyWindows(szNewKeyName, keyinfo[LUNAME].name, keyinfo[LUNAME].data))
  780.       return 1;
  781.  
  782.   // If conversation security is on, write out users & passwords
  783.   if (iConvSec)
  784.     {
  785.       keyinfo[USER].name = (char *) malloc(MAXUSERNAMELEN + 1);
  786.       nCount = SendMessage(hList, LB_GETCOUNT, 0, 0);
  787.  
  788.       // Read usernames and passwords from list box & create keys
  789.       for (i = 0; i < nCount; i++)
  790.         {
  791.           // Get username & make it the name of this key
  792.           SendMessage(hList, LB_GETTEXT, (WPARAM) i,
  793.                                   (LPARAM) keyinfo[USER].name);
  794.  
  795.           // Get pointer to password and then get password
  796.           hLocalMem = (LOCALHANDLE) SendMessage(hList, LB_GETITEMDATA, i, 0);
  797.           keyinfo[USER].data = (char *) LocalLock(hLocalMem);
  798.  
  799.           if (WriteKeyWindows(szNewKeyName, keyinfo[USER].name,
  800.                                 keyinfo[USER].data))
  801.             {
  802.               LocalUnlock(hLocalMem);
  803.               return 1;
  804.             }
  805.           LocalUnlock(hLocalMem);
  806.         }
  807.       free(keyinfo[USER].name);
  808.  
  809.     }
  810.  
  811.   return 0;
  812. }
  813. /*****************************************************************************/
  814. /*
  815.  * WriteKeyWindows:  Write the given key out to WIN.INI
  816.  * Return 0 on success, nonzero otherwise.
  817.  */
  818. /*****************************************************************************/
  819. INT WriteKeyWindows(char *heading, char *keyname, char *value)
  820. {
  821.   if (WriteProfileString(heading, keyname, value) == FALSE)
  822.     {
  823.       DisplayError(hDialog, IDS_INIWRITEFAILED);
  824.       return 1;
  825.     }
  826.   return 0;
  827. }
  828.  
  829. #endif //ifdef WIN32
  830.  
  831. /*****************************************************************************/
  832. /*
  833.  * ValidateField: Install a window procedure that handles messages to the
  834.  *   given edit window.
  835.  *
  836.  * Parameters: hwnd is the edit window, usType an identifier for the window
  837.  *   procuedure to install.  Currently the only allowed type is VALIDATE_INT,
  838.  *   which allows only digits to be typed into the edit box.
  839.  *
  840.  * Returns:  TRUE if window procedure is successfully installed,
  841.  *   FALSE otherwise.
  842. /*****************************************************************************/
  843. BOOL ValidateField( HWND hwnd, USHORT usType)
  844. {
  845.         FARPROC lpfn = NULL;
  846.         LONG        lPrevWndProc;
  847.         LONG FAR *lplNewWndProc;
  848.  
  849.         if (hwnd == NULL) {
  850.                 return(FALSE);
  851.         }
  852.  
  853.         lPrevWndProc = GetWindowLong( hwnd, GWL_WNDPROC );
  854.  
  855.         if (lPrevWndProc == 0) {
  856.                 return(FALSE);
  857.         }
  858.  
  859.         // check that the old wndproc is the same as the one we have stored
  860.         // also, get the new wndproc, based on contents of edit field
  861.  
  862.         // We only need two validation procedures, but if you want others
  863.         // (such as hexadecimal, filenames, etc.) you can add extra cases
  864.         // and procedures below.
  865.  
  866.         switch (usType) {
  867.           case VALIDATE_INT:
  868.             if (lpfnInt == NULL) {
  869.               lpfnInt = MakeProcInstance( (FARPROC)ValidateFieldInt, hInst );
  870.             }
  871.             lpfn = lpfnInt;
  872.  
  873.             lplNewWndProc = (LONG FAR *) &lPrevWndProcInt;
  874.             break;
  875.  
  876.           case VALIDATE_APPC:
  877.             if (lpfnAppc == NULL) {
  878.               lpfnAppc = MakeProcInstance( (FARPROC)ValidateFieldAppc, hInst );
  879.             }
  880.             lpfn = lpfnAppc;
  881.  
  882.             lplNewWndProc = (LONG FAR *) &lPrevWndProcAppc;
  883.             break;
  884.  
  885.           default:
  886.             return(FALSE);
  887.           }
  888.  
  889.         if (*lplNewWndProc == (LONG) NULL) {
  890.  
  891.                 *lplNewWndProc = lPrevWndProc;
  892.  
  893.         } else {
  894.  
  895.                 if (*lplNewWndProc != lPrevWndProc) {
  896.                         return(FALSE);
  897.                 }
  898.  
  899.                 *lplNewWndProc = lPrevWndProc;
  900.         }
  901.  
  902.         if (lpfn == NULL ||
  903.                 SetWindowLong(hwnd, GWL_WNDPROC, (LONG) lpfn) == (LONG) NULL) {
  904.  
  905.                 return(FALSE);
  906.         }
  907.         return(TRUE);
  908. }
  909.  
  910. /*****************************************************************************/
  911. /*
  912.  * ValidateFieldInt: A window proc that allows only 0 through 9 to be typed to
  913.  *   an edit box.
  914.  */
  915. /*****************************************************************************/
  916. LRESULT CALLBACK ValidateFieldInt( HWND hwnd, WORD msg, WPARAM wParam, LPARAM lParam )
  917. {
  918.         if (msg == WM_CHAR) {
  919.  
  920.                 char ch = LOBYTE(LOWORD(wParam));
  921.  
  922.                 if ((ch < '0' || ch > '9') && ch != VK_BACK )
  923.                   return(0);
  924.         }
  925.         // If character was legal or message wasn't WM_CHAR,
  926.         // pass message on to default window proc.
  927.         return CallWindowProc( lPrevWndProcInt, hwnd, msg, wParam, lParam);
  928. }
  929. /*****************************************************************************/
  930. /*
  931.  * ValidateFieldAppc: A window proc that allows only legal APPC name characters
  932.  *    to be typed to an edit box.
  933.  */
  934. /*****************************************************************************/
  935. LRESULT CALLBACK ValidateFieldAppc( HWND hwnd, WORD msg, WPARAM wParam, LPARAM lParam )
  936. {
  937.     if (msg == WM_CHAR) {
  938.  
  939.         char ch = LOBYTE(LOWORD(wParam));
  940.  
  941.         if (!(ch >= '0' && ch <= '9' ||
  942.             ch >= 'A' && ch <= 'Z' ||
  943.             ch >= 'a' && ch <= 'z' ||
  944.             ch == '@'   ||
  945.             ch == '#'   ||
  946.             ch == '$'   ||
  947.             ch == '%'   ||
  948.             ch == VK_BACK ))
  949.             return(0);
  950.     }
  951.     return CallWindowProc( lPrevWndProcAppc, hwnd, msg, wParam, lParam);
  952. }
  953. /*****************************************************************************/
  954. /*
  955.  * InstallCallback: Install a callback window proc.
  956.  *   Arguments:
  957.  *        hDlg: Handle of the parent dialog
  958.  *      uId: ID of window for which callback will be installed
  959.  *      OldWndProc: Handle for previous window proc
  960.  *      NewWndProc: Pointer to new window proc
  961.  */
  962. /*****************************************************************************/
  963. void InstallCallback(HWND hDlg, UINT uId, WNDPROC *OldWndProc, FARPROC NewWndProc)
  964. {
  965.   HANDLE hTemp;
  966.  
  967.   hTemp = GetDlgItem(hDlg, uId);
  968.   *OldWndProc = (WNDPROC) GetWindowLong(hTemp, GWL_WNDPROC);
  969.   SetWindowLong(hTemp, GWL_WNDPROC,
  970.                (LONG) MakeProcInstance( (FARPROC)NewWndProc, hInst));
  971. }
  972. /*****************************************************************************/
  973. /*
  974.  * InfiniteWndProc: Disable "timeout" box when the "infinite"
  975.  *   button is pressed, and enable it when "finite" is pressed.
  976.  */
  977. /*****************************************************************************/
  978. LRESULT CALLBACK InfiniteWndProc( HWND hwnd, WORD msg, WPARAM wParam, LPARAM lParam )
  979. {
  980.   if (msg == BM_SETCHECK)
  981.     // wParam is 0 if button is being turned off, 1 if being turned on
  982.     EnableWindow(hTimeout, (wParam == 0));
  983.   return CallWindowProc(lPrevWndProcInfinite, hwnd, msg, wParam, lParam);
  984. }
  985.  
  986.  
  987. /*****************************************************************************/
  988. /*
  989.  * ServiceWndProc: Disable "queued" box when the "service" button is pressed
  990.  */
  991. /*****************************************************************************/
  992. LRESULT CALLBACK ServiceWndProc( HWND hwnd, WORD msg, WPARAM wParam, LPARAM lParam )
  993. {
  994.   HWND hwndCntl;
  995.  
  996.   if (msg == BM_SETCHECK) {
  997.     //
  998.     // wParam is 0 if button is being turned off, 1 if being turned on
  999.     //
  1000.     hwndCntl = GetDlgItem( hDialog, IDC_QUEUED );
  1001.     EnableWindow( hwndCntl, wParam == 1 ? FALSE : TRUE );
  1002.  
  1003.     if ( wParam == 1 ) {
  1004.         SendMessage( hwndCntl, BM_SETCHECK, 1, 0 );
  1005.     }
  1006.   }
  1007.  
  1008.   return CallWindowProc(lPrevWndProcService, hwnd, msg, wParam, lParam);
  1009. }
  1010.  
  1011.  
  1012. /*****************************************************************************/
  1013. /*
  1014.  * ConvSecWndProc: Window proc for "conversation security" checkbox.  Grey
  1015.  *   out "users" group box when checkbox is off
  1016.  */
  1017. /*****************************************************************************/
  1018. LRESULT CALLBACK ConvSecWndProc( HWND hwnd, WORD msg, WPARAM wParam, LPARAM lParam )
  1019. {
  1020.   int nItems = SendMessage(hList, LB_GETCOUNT, 0, 0L);
  1021.   if (msg == BM_SETCHECK)
  1022.     {
  1023.       EnableWindow(GetDlgItem(hDialog, IDC_USERBOX), wParam == 1 ? TRUE : FALSE);
  1024.       EnableWindow(GetDlgItem(hDialog, IDC_ADD), wParam == 1 ? TRUE : FALSE);
  1025.       EnableWindow(hList, wParam == 1 ? TRUE : FALSE);
  1026.       // Only turn on Delete & Edit if there's something in the list box
  1027.       EnableWindow(GetDlgItem(hDialog, IDC_DELETE), wParam == 1 && nItems);
  1028.       EnableWindow(GetDlgItem(hDialog, IDC_EDIT), wParam == 1 && nItems);
  1029.     }
  1030.   return CallWindowProc(lPrevWndProcConvSec, hwnd, msg, wParam, lParam);
  1031. }
  1032.  
  1033.  
  1034. /*****************************************************************************/
  1035. /*
  1036.  * DeleteListItem: Handle all aspects of deleting a user from the list box,
  1037.  *   given his index.
  1038.  */
  1039. /*****************************************************************************/
  1040. void DeleteListItem(INT nIndex)
  1041. {
  1042.   LOCALHANDLE hLocalMem;
  1043.   INT nUsers;
  1044.  
  1045.   // Free associated password memory, if necessary
  1046.   if (hLocalMem = (LOCALHANDLE) SendMessage(hList, LB_GETITEMDATA,
  1047.                                             nIndex, 0))
  1048.     LocalFree(hLocalMem);
  1049.  
  1050.   // Simply remove currently selected user
  1051.   SendMessage(hList, LB_DELETESTRING, (WPARAM) nIndex, 0);
  1052.  
  1053.   nUsers = SendMessage(hList, LB_GETCOUNT, 0, 0);
  1054.  
  1055.   // Disable Delete & Edit buttons if no one is left
  1056.   if (0 == nUsers)
  1057.     {
  1058.       EnableWindow(GetDlgItem(hDialog, IDC_DELETE), FALSE);
  1059.       EnableWindow(GetDlgItem(hDialog, IDC_EDIT), FALSE);
  1060.       return;
  1061.     }
  1062.  
  1063.   // Move the highlight:
  1064.   // 1) If the deleted user was the last one in the list, go to previous user.
  1065.   // 2) Otherwise go to the next user.
  1066.   SendMessage(hList, LB_SETCURSEL,
  1067.               (nUsers == nIndex) ? (nIndex - 1) : nIndex, 0);
  1068. }
  1069. /*****************************************************************************/
  1070. /*
  1071.  * ReadString: Get the text from an edit box and turn it into a C string.
  1072.  *
  1073.  * Parameters:
  1074.  *
  1075.  * hDlg:  The dialog box.
  1076.  * id: The dialog item id.
  1077.  * lpString:  A double pointer to a character.  ReadString reserves space
  1078.  *            for the incoming string.
  1079.  * maxlen: The maximum number of characters to read from the edit box.
  1080.  *
  1081.  * Returns:
  1082.  *   The number of characters read from the edit box.
  1083.  */
  1084. /*****************************************************************************/
  1085. INT ReadString(HWND hDlg, INT id, char **lpString, INT maxlen)
  1086. {
  1087.   // Leave space for null character
  1088.   *lpString = malloc((maxlen + 1) * sizeof(char));
  1089.  
  1090.   return GetDlgItemText(hDlg, id, *lpString, maxlen+1);
  1091. }
  1092. /*****************************************************************************/
  1093. /*
  1094.  * DisplayError: Bring up MessageBox with given message code's string.
  1095.  */
  1096. /*****************************************************************************/
  1097. void DisplayError( HWND hwnd, UINT uError)
  1098. {
  1099.         char        sz[256], szFormat[256];
  1100.  
  1101.         if ( LoadString( hInst, uError, sz, sizeof(sz)) == 0 ) {
  1102.                 LoadString( hInst, IDS_UNKNOWN, szFormat, sizeof(szFormat) );
  1103.                 sprintf( sz, szFormat, uError );
  1104.         }
  1105.  
  1106.         LoadString( hInst, IDS_ERRORTITLE, szFormat, sizeof(szFormat) );
  1107.         MessageBox( hwnd, sz, szFormat, MB_ICONEXCLAMATION | MB_OK);
  1108. }
  1109. /*****************************************************************************/
  1110. /*
  1111.  * DisplayInfo:  Put up an information box with given message string.
  1112.  */
  1113. /*****************************************************************************/
  1114. void DisplayInfo( HWND hwnd, UINT uInfo)
  1115. {
  1116.         char        sz[256], szFormat[256];
  1117.  
  1118.         if ( LoadString( hInst, uInfo, sz, sizeof(sz) ) == 0 ) {
  1119.                 LoadString( hInst, IDS_NOMESSAGE, szFormat, sizeof(szFormat) );
  1120.                 sprintf( sz, szFormat, uInfo );
  1121.         }
  1122.  
  1123.         LoadString( hInst, IDS_INFOTITLE, szFormat, sizeof(szFormat) );
  1124.         MessageBox( hwnd, sz, szFormat, MB_ICONINFORMATION | MB_OK);
  1125. }
  1126.  
  1127. void MakeValidLUName(LPTSTR name) {
  1128.     int o, n;
  1129.     char oldname[MAX_COMPUTERNAME_LENGTH + 1], c;
  1130.     int l;
  1131.  
  1132.     strcpy(oldname, name);
  1133.  
  1134.     l = strlen(oldname);
  1135.     for (o = 0, n = 0; o < l; o++) {
  1136.         c = toupper(oldname[o]);
  1137.  
  1138.         // make sure this is a valid character
  1139.         if (((c >= '0') && (c <= '9')) ||
  1140.             ((c >= 'A') && (c <= 'Z')) ||
  1141.             (c == '@') ||
  1142.             (c == '#') ||
  1143.             (c == '$') ||
  1144.             (c == '%') ||
  1145.             (c == ' ')) name[n++] = c;
  1146.  
  1147.         // only allow 8 characters in a LU name
  1148.         if (n == 8) break;
  1149.     }
  1150.     name[n] = 0;
  1151. }
  1152.