Index: trunk/src/helpers/dialog.c
===================================================================
--- trunk/src/helpers/dialog.c	(revision 255)
+++ trunk/src/helpers/dialog.c	(revision 257)
@@ -296,6 +296,6 @@
  *      the control _sizes_, and everything is layed out automatically.
  *      You may even have the formatter compute the sizes automatically
- *      based on the control classes and values; it is possible to
- *      create dialogs without specifying a single size also.
+ *      based on the control classes and values (strings); it is possible
+ *      to create dialogs without specifying a single size also.
  *
  *      There are several tricks to how this works.
@@ -314,5 +314,5 @@
  *          (which again goes into ProcessRow and ProcessColumn).
  *          There is no limit to how deep tables may nest, except
- *          the stack size of the current thread. ;-)
+ *          for the stack size of the current thread, of course.
  *
  *      3)  This whole recursive iteration is performed several times.
Index: trunk/src/helpers/dosh.c
===================================================================
--- trunk/src/helpers/dosh.c	(revision 255)
+++ trunk/src/helpers/dosh.c	(revision 257)
@@ -2588,4 +2588,5 @@
  *@@changed V0.9.16 (2001-12-18) [umoeller]: fixed error codes
  *@@changed V1.0.1 (2003-01-10) [umoeller]: now allowing read for all modes
+ *@@changed V1.0.2 (2003-11-13) [umoeller]: optimized; now calling doshQueryPathSize only on failure
  */
 
@@ -2615,6 +2616,7 @@
             // which isn't that meaningful
             // V0.9.16 (2001-12-08) [umoeller]
-            arc = doshQueryPathSize(pcszFilename,
-                                    pcbFile);
+            /* arc = doshQueryPathSize(pcszFilename,
+                                    pcbFile); */
+                // moved this down V1.0.2 (2003-11-13) [umoeller]
         break;
 
@@ -2625,6 +2627,7 @@
                           | OPEN_ACCESS_READWRITE;
 
-            arc = doshQueryPathSize(pcszFilename,
-                                    pcbFile);
+            /* arc = doshQueryPathSize(pcszFilename,
+                                    pcbFile); */
+                // moved this down V1.0.2 (2003-11-13) [umoeller]
         break;
 
@@ -2646,5 +2649,9 @@
     }
 
-    if ((!arc) && fsOpenFlags && pcbFile && ppFile)
+    if (    (!arc)
+         && fsOpenFlags
+         && pcbFile
+         && ppFile
+       )
     {
         PXFILE pFile;
@@ -2695,9 +2702,20 @@
                 pFile->pszFilename = strdup(pcszFilename);
             }
-            #ifdef DEBUG_DOSOPEN
             else
-                 _Pmpf((__FUNCTION__ ": DosOpen returned %d for %s",
+            {
+                #ifdef DEBUG_DOSOPEN
+                    _Pmpf((__FUNCTION__ ": DosOpen returned %d for %s",
                              arc, pcszFilename));
-            #endif
+                #endif
+
+                // open failed: if the file doesn't exist, DosOpen only
+                // returns OPEN_FAILED, while ERROR_FILE_NOT_FOUND would
+                // be a bit more informative
+                // (this check used to be before DosOpen, but is a bit
+                // excessive and should only be run if we really have no open)
+                if (arc == ERROR_OPEN_FAILED)
+                    arc = doshQueryPathSize(pcszFilename,
+                                            pcbFile);
+            }
 
             if (arc)
@@ -2750,5 +2768,6 @@
  *@@added V0.9.13 (2001-06-14) [umoeller]
  *@@changed V0.9.16 (2001-12-18) [umoeller]: now with XFILE, and always using FILE_BEGIN
- *@@chaanged V0.9.19 (2002-04-02) [umoeller]: added params checking
+ *@@changed V0.9.19 (2002-04-02) [umoeller]: added params checking
+ *@@changed V1.0.2 (2003-11-13) [umoeller]: optimized cache (using realloc)
  */
 
@@ -2828,4 +2847,5 @@
             #endif
 
+#if 0
             // free old cache
             if (pFile->pbCache)
@@ -2834,4 +2854,9 @@
             // allocate new cache
             if (!(pFile->pbCache = (PBYTE)malloc(pFile->cbCache)))
+#else
+            // realloc is better V1.0.2 (2003-11-13) [umoeller]
+            if (!(pFile->pbCache = (PBYTE)realloc(pFile->pbCache,
+                                                  pFile->cbCache)))
+#endif
                 arc = ERROR_NOT_ENOUGH_MEMORY;
             else
@@ -4487,4 +4512,24 @@
 
 /*
+ *@@ doshMyParentPID:
+ *      returns the PID of the parent of the current process.
+ *
+ *      This uses an interesting hack which is way
+ *      faster than DosGetInfoBlocks.
+ *
+ *@@added V1.0.2 (2003-11-13) [umoeller]
+ */
+
+ULONG doshMyParentPID(VOID)
+{
+    if (!G_pvLocalInfoSeg)
+        // first call:
+        GetInfoSegs();
+
+    // parent PID is at offset 2 in the local info seg
+    return *(PUSHORT)((PBYTE)G_pvLocalInfoSeg + 2);
+}
+
+/*
  *@@ doshMyTID:
  *      returns the TID of the current thread.
Index: trunk/src/helpers/exeh.c
===================================================================
--- trunk/src/helpers/exeh.c	(revision 255)
+++ trunk/src/helpers/exeh.c	(revision 257)
@@ -266,14 +266,14 @@
 
         // read old DOS EXE header
-        if (!(pExec->pDosExeHeader = (PDOSEXEHEADER)malloc(sizeof(DOSEXEHEADER))))
-            arc = ERROR_NOT_ENOUGH_MEMORY;
-        else if (!(arc = doshReadAt(pFile,
-                                    0,
-                                    &pExec->cbDosExeHeader,      // in/out
-                                    (PBYTE)pExec->pDosExeHeader,
-                                    DRFL_FAILIFLESS)))
+        if ((arc = doshReadAt(pFile,
+                              0,
+                              &pExec->cbDosExeHeader,      // in/out
+                              (PBYTE)&pExec->DosExeHeader,
+                              DRFL_FAILIFLESS)))
+            pExec->cbDosExeHeader = 0;
+        else
         {
             // now check if we really have a DOS header
-            if (pExec->pDosExeHeader->usDosExeID != 0x5a4d)
+            if (pExec->DosExeHeader.usDosExeID != 0x5a4d)
             {
                 // arc = ERROR_INVALID_EXE_SIGNATURE;
@@ -289,5 +289,5 @@
                 // remove the DOS header info, since we have none
                 // V0.9.12 (2001-05-03) [umoeller]
-                FREE(pExec->pDosExeHeader);
+                // FREE(pExec->pDosExeHeader);
                 pExec->cbDosExeHeader = 0;
             }
@@ -295,5 +295,5 @@
             {
                 // we have a DOS header:
-                if (pExec->pDosExeHeader->usRelocTableOfs < 0x40)
+                if (pExec->DosExeHeader.usRelocTableOfs < 0x40)
                 {
                     // neither LX nor PE nor NE:
@@ -305,5 +305,5 @@
                     // we have a new header offset:
                     fLoadNewHeader = TRUE;
-                    ulNewHeaderOfs = pExec->pDosExeHeader->ulNewHeaderOfs;
+                    ulNewHeaderOfs = pExec->DosExeHeader.ulNewHeaderOfs;
                 }
             }
@@ -1199,7 +1199,7 @@
         ULONG ulNewHeaderOfs = 0;       // V0.9.12 (2001-05-03) [umoeller]
 
-        if (pExec->pDosExeHeader)
+        if (pExec->cbDosExeHeader)
             // executable has DOS stub: V0.9.12 (2001-05-03) [umoeller]
-            ulNewHeaderOfs = pExec->pDosExeHeader->ulNewHeaderOfs;
+            ulNewHeaderOfs = pExec->DosExeHeader.ulNewHeaderOfs;
 
         if (pExec->ulExeFormat == EXEFORMAT_LX)
@@ -1353,7 +1353,7 @@
     HFILE hfExe = pExec->pFile->hf;
 
-    if (pExec->pDosExeHeader)
+    if (pExec->cbDosExeHeader)
         // executable has DOS stub: V0.9.12 (2001-05-03) [umoeller]
-        ulNewHeaderOfs = pExec->pDosExeHeader->ulNewHeaderOfs;
+        ulNewHeaderOfs = pExec->DosExeHeader.ulNewHeaderOfs;
 
     ENSURE(DosSetFilePtr(hfExe,
@@ -1594,7 +1594,7 @@
     HFILE hfExe = pExec->pFile->hf;
 
-    if (pExec->pDosExeHeader)
+    if (pExec->cbDosExeHeader)
         // executable has DOS stub: V0.9.12 (2001-05-03) [umoeller]
-        ulNewHeaderOfs = pExec->pDosExeHeader->ulNewHeaderOfs;
+        ulNewHeaderOfs = pExec->DosExeHeader.ulNewHeaderOfs;
 
     ENSURE(DosSetFilePtr(hfExe,
@@ -1801,7 +1801,7 @@
         ULONG ulNewHeaderOfs = 0; // V0.9.12 (2001-05-03) [umoeller]
 
-        if (pExec->pDosExeHeader)
+        if (pExec->cbDosExeHeader)
             // executable has DOS stub: V0.9.12 (2001-05-03) [umoeller]
-            ulNewHeaderOfs = pExec->pDosExeHeader->ulNewHeaderOfs;
+            ulNewHeaderOfs = pExec->DosExeHeader.ulNewHeaderOfs;
 
         if (pExec->ulExeFormat == EXEFORMAT_LX)
@@ -1976,7 +1976,7 @@
         ULONG           ulNewHeaderOfs = 0; // V0.9.12 (2001-05-03) [umoeller]
 
-        if (pExec->pDosExeHeader)
+        if (pExec->cbDosExeHeader)
             // executable has DOS stub: V0.9.12 (2001-05-03) [umoeller]
-            ulNewHeaderOfs = pExec->pDosExeHeader->ulNewHeaderOfs;
+            ulNewHeaderOfs = pExec->DosExeHeader.ulNewHeaderOfs;
 
         if (pExec->ulExeFormat == EXEFORMAT_LX)
@@ -2371,7 +2371,7 @@
         ULONG cb;
 
-        if (pExec->pDosExeHeader)
+        if (pExec->cbDosExeHeader)
             // executable has DOS stub: V0.9.12 (2001-05-03) [umoeller]
-            ulNewHeaderOfs = pExec->pDosExeHeader->ulNewHeaderOfs;
+            ulNewHeaderOfs = pExec->DosExeHeader.ulNewHeaderOfs;
 
         // resource table
@@ -2883,4 +2883,10 @@
         ULONG ulPageSize = pExec->pLXHeader->ulPageSize;
 
+        // if the data is not compressed, we read it directly
+        // into caller's pbData buffer (avoid one memcpy)
+        // V1.0.2 (2003-11-13) [umoeller]
+        PBYTE pbTarget =
+            (ulFlags == VALID) ? pbData : pabCompressed;
+
         ulOffset += ulExeOffset;
 
@@ -2898,5 +2904,5 @@
                                     ulOffset,
                                     &ulSize,
-                                    pabCompressed,
+                                    pbTarget, // pabCompressed, V1.0.2 (2003-11-13) [umoeller]
                                     0)))
         {
@@ -2924,4 +2930,5 @@
                 break;
 
+                /* V1.0.2 (2003-11-13) [umoeller]
                 case VALID:
                     // uncompressed
@@ -2930,4 +2937,5 @@
                            ulPageSize);
                 break;
+                */
             }
         }
@@ -2948,8 +2956,22 @@
  *      type _and_ ID.
  *
- *      If NO_ERROR is returned, *ppbResData receives
- *      a new buffer with the raw resource data, and
- *      *pcbResData receives the size of that buffer.
- *      The caller must then free() that buffer.
+ *      If NO_ERROR is returned,
+ *
+ *      --  *ppbResData receives a new buffer with
+ *          the raw resource data, which the caller
+ *          must free();
+ *
+ *      --  *pulOffset receives an offset into that
+ *          buffer, where the actual resource data
+ *          starts;
+ *
+ *      --  *pcbResData receives the size of the
+ *          following resource data (what follows
+ *          after *pulOffset).
+ *
+ *      The reason for this slightly complicated
+ *      format is to avoid another memcpy since
+ *      resource data need not necessarily be on
+ *      an LX page boundary.
  *
  *      This code will properly unpack compressed
@@ -2977,4 +2999,5 @@
                           ULONG idResource,      // in: resource ID or 0 for first
                           PBYTE *ppbResData,     // out: resource data (to be free()'d)
+                          PULONG pulOffset,      // out: offset of actual data in buffer
                           PULONG pcbResData)     // out: size of resource data (ptr can be NULL)
 {
@@ -2995,7 +3018,7 @@
         return ERROR_INVALID_EXE_SIGNATURE;
 
-    if (pExec->pDosExeHeader)
+    if (pExec->cbDosExeHeader)
         // executable has DOS stub: V0.9.12 (2001-05-03) [umoeller]
-        ulNewHeaderOfs = pExec->pDosExeHeader->ulNewHeaderOfs;
+        ulNewHeaderOfs = pExec->DosExeHeader.ulNewHeaderOfs;
 
     if (!(cResources = pLXHeader->ulResTblCnt))
@@ -3136,24 +3159,32 @@
                         if (!arc)
                         {
-                            // allocate a new buffer for caller
-                            if (!(*ppbResData = (PBYTE)malloc(pRsEntry->cb)))
-                                arc = ERROR_NOT_ENOUGH_MEMORY;
-                            else
-                            {
-                                // copy into that buffer from the offset
-                                // into the first page and the data from
-                                // the subsequent pages too
-                                memcpy(*ppbResData,
-                                       pabUncompressed + ulResOffsetInFirstPage,
-                                       pRsEntry->cb);
-
-                                if (pcbResData)
-                                    *pcbResData = pRsEntry->cb;
-                            }
+                            // new code without malloc/memcpy V1.0.2 (2003-11-13) [umoeller]
+                            *ppbResData = pabUncompressed;
+                            *pulOffset = ulResOffsetInFirstPage;
+
+
+                            /*
+                                // allocate a new buffer for caller
+                                if (!(*ppbResData = (PBYTE)malloc(pRsEntry->cb)))
+                                    arc = ERROR_NOT_ENOUGH_MEMORY;
+                                else
+                                {
+                                    // copy into that buffer from the offset
+                                    // into the first page and the data from
+                                    // the subsequent pages too
+                                    memcpy(*ppbResData,
+                                           pabUncompressed + ulResOffsetInFirstPage,
+                                           pRsEntry->cb);
+                                }
+                            */
+
+                            if (pcbResData)
+                                *pcbResData = pRsEntry->cb;
 
                             fPtrFound = TRUE;
                         }
-
-                        FREE(pabUncompressed);
+                        else
+                            FREE(pabUncompressed);
+
                         FREE(pabCompressed);
                     }
@@ -3239,7 +3270,7 @@
         ULONG cb;
 
-        if (pExec->pDosExeHeader)
+        if (pExec->cbDosExeHeader)
             // executable has DOS stub: V0.9.12 (2001-05-03) [umoeller]
-            ulNewHeaderOfs = pExec->pDosExeHeader->ulNewHeaderOfs;
+            ulNewHeaderOfs = pExec->DosExeHeader.ulNewHeaderOfs;
 
         // resource table
@@ -3351,7 +3382,7 @@
         return ERROR_INVALID_EXE_SIGNATURE;
 
-    if (pExec->pDosExeHeader)
+    if (pExec->cbDosExeHeader)
         // executable has DOS stub: V0.9.12 (2001-05-03) [umoeller]
-        ulNewHeaderOfs = pExec->pDosExeHeader->ulNewHeaderOfs;
+        ulNewHeaderOfs = pExec->DosExeHeader.ulNewHeaderOfs;
 
     // _Pmpf((__FUNCTION__ ": entering, checking %d resources", pNEHeader->usResSegmCount));
@@ -3454,5 +3485,5 @@
         char **papsz[] =
             {
-                (char**)&pExec->pDosExeHeader,
+                // (char**)&pExec->pDosExeHeader,
                 (char**)&pExec->pNEHeader,
                 (char**)&pExec->pLXHeader,
Index: trunk/src/helpers/helpers_pre.in
===================================================================
--- trunk/src/helpers/helpers_pre.in	(revision 255)
+++ trunk/src/helpers/helpers_pre.in	(revision 257)
@@ -47,4 +47,5 @@
 $(OUTPUTDIR)\xprf.obj\
 $(OUTPUTDIR)\xprf2.obj\
+$(OUTPUTDIR)\xsecapi.obj\
 $(OUTPUTDIR)\xstring.obj
 
Index: trunk/src/helpers/lan.c
===================================================================
--- trunk/src/helpers/lan.c	(revision 255)
+++ trunk/src/helpers/lan.c	(revision 257)
@@ -213,4 +213,36 @@
  *      queries the given service.
  *
+ *      If NO_ERROR is returned,
+ +
+ +          SERVICEBUF.svci2_status & SERVICE_INSTALL_STATE
+ +
+ *      can be queried for the service state, which should
+ *      be one of the following:
+ *
+ *      --  SERVICE_UNINSTALLED: not running. For the
+ *          REQUESTER service, this is only a theoretical
+ *          value because without it, NERR_WkstaNotStarted
+ *          (2138) is returned.
+ *
+ *      --  SERVICE_INSTALL_PENDING: start in progress
+ *
+ *      --  SERVICE_UNINSTALL_PENDING: stop in progress
+ *
+ *      --  SERVICE_INSTALLED: running
+ *
+ *      Alternatively, call lanServiceControl with the
+ *      SERVICE_CTRL_INTERROGATE code, which actually asks the
+ *      service.
+ *
+ *      Returns, among others:
+ *
+ *      --  NO_ERROR
+ *
+ *      --  NERR_WkstaNotStarted (2138): requester is not
+ *          running.
+ *
+ *      --  NERR_ServiceNotInstalled: requested service is
+ *          not running.
+ *
  *@@added V1.0.0 (2002-09-24) [umoeller]
  */
@@ -240,5 +272,15 @@
  *      must be fully qualified so you cannot
  *      abbreviate "requester" with "req", for
- *      example (as valid with the net command).
+ *      example (as valid with the NET START command).
+ *
+ *      The name of the service is found in the IBMLAN.INI file.
+ *      The executable file name of the service is matched to a
+ *      corresponding entry in the Services section of the
+ *      IBMLAN.INI file. Any relative file path name supplied
+ *      for a service is assumed to be relative to the LAN
+ *      Server root directory (\IBMLAN).
+ *
+ *      #Net32ServiceInstall supports a cmdargs argument, which
+ *      is presently always passed as NULL with this implementation.
  *
  *      Returns, among others:
@@ -277,9 +319,14 @@
 /*
  *@@ lanServiceControl:
- *      queries, pauses, resumes, or stops the given service.
+ *      queries, pauses, resumes, or stops the given service. This
+ *      has the functionality of the NET STOP command.
  *
  *      opcode must be one of:
  *
  *      --  SERVICE_CTRL_INTERROGATE (0): interrogate service status.
+ *          This is similar to running lanServiceGetInfo, except
+ *          that this one actually asks the service for its status,
+ *          while lanServiceGetInfo simply dumps the status last
+ *          posted.
  *
  *      --  SERVICE_CTRL_PAUSE (1): pause service.
Index: trunk/src/helpers/nls.c
===================================================================
--- trunk/src/helpers/nls.c	(revision 255)
+++ trunk/src/helpers/nls.c	(revision 257)
@@ -149,4 +149,5 @@
                      ++n)
                     G_afLeadByte[n] = TRUE;
+
                 G_fDBCS = TRUE;
             }
Index: trunk/src/helpers/nlscache.c
===================================================================
--- trunk/src/helpers/nlscache.c	(revision 255)
+++ trunk/src/helpers/nlscache.c	(revision 257)
@@ -289,25 +289,27 @@
  *      After that, this function implements a fast string
  *      cache for various NLS strings. Compared to the
- *      standard method, this has the following advantages:
- *
- *      -- Memory is only consumed for strings that are actually
- *         used. The NLSSTRINGS array had become terribly big,
- *         and lots of strings were loaded that were never used.
- *
- *      -- Program startup should be a bit faster because we don't
- *         have to load a thousand strings at startup.
- *
- *      -- The memory buffer holding the string is probably close
- *         to the rest of the heap data that the caller allocated,
- *         so this might lead to less memory page fragmentation.
- *
- *      -- To add a new NLS string, before this mechanism existed,
- *         three files had to be changed (and kept in sync): common.h
- *         to add a field to the NLSSTRINGS structure, dlgids.h to
- *         add the string ID, and xfldrXXX.rc to add the resource.
- *         With the new mechanism, there's no need to change common.h
- *         any more, so the danger of forgetting something is a bit
- *         reduced. Anyway, fewer recompiles are needed (maybe),
- *         and sending in patches to the code is a bit easier.
+ *      standard method of preloading all NLS strings at
+ *      program startup, this method of on-demand string
+ *      loading has the following advantages:
+ *
+ *      --  Memory is only consumed for strings that are actually
+ *          used.
+ *
+ *      --  Program startup should be a bit faster because we don't
+ *          have to load a thousand strings at startup.
+ *
+ *      --  The memory buffer holding the string is probably close
+ *          to the rest of the heap data that the caller allocated,
+ *          so this might lead to less memory page fragmentation.
+ *          (This is a wild guess though.)
+ *
+ *      --  To add a new NLS string, before this mechanism existed,
+ *          three files had to be changed (and kept in sync): common.h
+ *          to add a field to the NLSSTRINGS structure, dlgids.h to
+ *          add the string ID, and xfldrXXX.rc to add the resource.
+ *          With the new mechanism, there's no need to change common.h
+ *          any more, so the danger of forgetting something is a bit
+ *          reduced. Anyway, fewer recompiles are needed (maybe),
+ *          and sending in patches to the code is a bit easier.
  *
  *      On input, specify a string resouce ID that exists
Index: trunk/src/helpers/xsecapi.c
===================================================================
--- trunk/src/helpers/xsecapi.c	(revision 257)
+++ trunk/src/helpers/xsecapi.c	(revision 257)
@@ -0,0 +1,782 @@
+
+/*
+ *@@sourcefile xsecapi.c:
+ *
+ *@@header "shared\xsecapi.h"
+ */
+
+/*
+ *      Copyright (C) 2001-2003 Ulrich Mller.
+ *
+ *      This file is part of the XWorkplace source package.
+ *      XWorkplace is free software; you can redistribute it and/or modify
+ *      it under the terms of the GNU General Public License as published
+ *      by the Free Software Foundation, in version 2 as it comes in the
+ *      "COPYING" file of the XWorkplace main distribution.
+ *      This program is distributed in the hope that it will be useful,
+ *      but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *      GNU General Public License for more details.
+ */
+
+#pragma strings(readonly)
+
+/*
+ *  Suggested #include order:
+ *  1)  os2.h
+ *  2)  C library headers
+ *  3)  setup.h (code generation and debugging options)
+ *  4)  headers in helpers\
+ *  5)  at least one SOM implementation header (*.ih)
+ *  6)  dlgids.h, headers in shared\ (as needed)
+ *  7)  headers in implementation dirs (e.g. filesys\, as needed)
+ *  8)  #pragma hdrstop and then more SOM headers which crash with precompiled headers
+ */
+
+#define INCL_DOSPROCESS
+#define INCL_DOSSEMAPHORES
+#define INCL_DOSQUEUES
+#define INCL_DOSERRORS
+#include <os2.h>
+
+// C library headers
+#include <stdio.h>
+
+// generic headers
+#include "setup.h"                      // code generation and debugging options
+
+// headers in /helpers
+#include "helpers\standards.h"          // some standard macros
+#include "helpers\stringh.h"            // string helper routines
+
+// XWorkplace implementation headers
+#include "helpers\xwpsecty.h"           // XWorkplace Security
+
+// other SOM headers
+#pragma hdrstop
+
+/* ******************************************************************
+ *
+ *   Helpers
+ *
+ ********************************************************************/
+
+/*
+ *@@ XWPSHELLCOMMAND:
+ *
+ *@@added V0.9.11 (2001-04-22) [umoeller]
+ */
+
+typedef struct _XWPSHELLCOMMAND
+{
+    PXWPSHELLQUEUEDATA  pShared;
+    PID                 pidXWPShell;
+    HQUEUE              hqXWPShell;
+} XWPSHELLCOMMAND, *PXWPSHELLCOMMAND;
+
+VOID FreeXWPShellCommand(PXWPSHELLCOMMAND pCommand);
+
+/*
+ *@@ CreateXWPShellCommand:
+ *      creates a command for XWPShell to process.
+ *
+ *      See XWPSHELLQUEUEDATA for a description of
+ *      what's going on here.
+ *
+ *      ulCommand must be one of QUECMD_* values.
+ *
+ *      If this returns NO_ERROR, the caller must
+ *      then fill in the shared data according to
+ *      what the command requires and use
+ *      SendXWPShellCommand then.
+ *
+ *      Among others, this can return:
+ *
+ *      --  ERROR_NOT_ENOUGH_MEMORY
+ *
+ *      --  ERROR_QUE_NAME_NOT_EXIST (343): XWPShell queue
+ *          not found, XWPShell is probably not running.
+ *
+ *@@added V0.9.11 (2001-04-22) [umoeller]
+ */
+
+APIRET CreateXWPShellCommand(ULONG ulCommand,               // in: command
+                             PXWPSHELLCOMMAND *ppCommand)   // out: cmd structure if NO_ERROR is returned
+{
+    APIRET      arc = NO_ERROR;
+
+    PXWPSHELLCOMMAND pCommand;
+    if (!(pCommand = NEW(XWPSHELLCOMMAND)))
+        arc = ERROR_NOT_ENOUGH_MEMORY;
+    else
+    {
+        ZERO(pCommand);
+
+        // check if XWPShell is running; if so the queue must exist
+        if (    (!(arc = DosOpenQueue(&pCommand->pidXWPShell,
+                                      &pCommand->hqXWPShell,
+                                      QUEUE_XWPSHELL)))
+             && (!(arc = DosAllocSharedMem((PVOID*)&pCommand->pShared,
+                                           NULL,     // unnamed
+                                           sizeof(XWPSHELLQUEUEDATA),
+                                           PAG_COMMIT | OBJ_GIVEABLE | PAG_READ | PAG_WRITE)))
+           )
+        {
+            PXWPSHELLQUEUEDATA pShared = pCommand->pShared;
+
+            if (    (!(arc = DosGiveSharedMem(pShared,
+                                              pCommand->pidXWPShell,
+                                              PAG_READ | PAG_WRITE)))
+                 && (!(arc = DosCreateEventSem(NULL,
+                                               &pShared->hevData,
+                                               DC_SEM_SHARED,
+                                               FALSE)))      // reset
+               )
+            {
+                pShared->ulCommand = ulCommand;
+                pShared->cbStruct = sizeof(XWPSHELLQUEUEDATA);
+            }
+        }
+
+        if (!arc)
+            *ppCommand = pCommand;
+        else
+            FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ SendXWPShellCommand:
+ *      sends a command to XWPShell and waits for
+ *      the command to be processed.
+ *
+ *      Be warned, this blocks the calling thread.
+ *      Even though XWPShell should be following
+ *      the PM 0.1 seconds rule in most cases,
+ *      some calls such as "create user" can be
+ *      expensive and you might want to start a
+ *      second thread for this.
+ *
+ *      Returns:
+ *
+ *      --  NO_ERROR: command written, and XWPShell
+ *          responded correctly.
+ *
+ *      --  ERROR_TIMEOUT (640): XWPShell did not
+ *          respond within five seconds.
+ *
+ *      --  XWPSEC_QUEUE_INVALID_CMD: XWPShell did
+ *          not recognize the given command code.
+ *
+ *      --  XWPSEC_STRUCT_MISMATCH: sizeof XWPSHELLQUEUEDATA
+ *          does not match; probably a wrong version of
+ *          XWPShell is installed.
+ *
+ *      --  XWPSEC_INTEGRITY: internal error in XWPShell.
+ *          Some data structure was corrupt, and this is
+ *          really a bug and should be fixed.
+ *
+ *      plus the many command-specific XWPSEC_*
+ *      error codes.
+ *
+ *@@added V0.9.11 (2001-04-22) [umoeller]
+ */
+
+APIRET SendXWPShellCommand(PXWPSHELLCOMMAND pCommand)
+{
+    APIRET arc = NO_ERROR;
+
+    if (!pCommand)
+        arc = ERROR_INVALID_PARAMETER;
+    else
+    {
+        if (    (!(arc = DosWriteQueue(pCommand->hqXWPShell,
+                                       (ULONG)pCommand->pShared,   // request data
+                                       0,
+                                       NULL,
+                                       0)))              // priority
+                // wait 5 seconds for XWPShell to write the data
+             && (!(arc = DosWaitEventSem(pCommand->pShared->hevData,
+                                         5*1000)))
+           )
+        {
+            // return what XWPShell returns
+            arc = pCommand->pShared->arc;
+        }
+    }
+
+    return arc;
+}
+
+/*
+ *@@ FreeXWPShellCommand:
+ *      cleans up.
+ *
+ *@@added V0.9.11 (2001-04-22) [umoeller]
+ */
+
+VOID FreeXWPShellCommand(PXWPSHELLCOMMAND pCommand)
+{
+    if (pCommand)
+    {
+        if (pCommand->pShared)
+        {
+            if (pCommand->pShared->hevData)
+                DosCloseEventSem(pCommand->pShared->hevData);
+
+            DosFreeMem(pCommand->pShared);
+        }
+
+        if (pCommand->hqXWPShell)
+            DosCloseQueue(pCommand->hqXWPShell);
+
+        free(pCommand);
+    }
+}
+
+/* ******************************************************************
+ *
+ *   XWPShell security APIs
+ *
+ ********************************************************************/
+
+/*
+ *@@ xsecQueryStatus:
+ *      tests whether XWorkplace security is working and
+ *      returns ring-0 statistics.
+ *
+ +      If this returns NO_ERROR, XWPShell is running
+ *      correctly. Check XWPSECSTATUS.fLocalSecurity to
+ *      learn whether the ring-0 driver is active also.
+ *
+ *      Required authority: None.
+ *
+ *      Command-specific error codes in addition to those
+ *      returned by CreateXWPShellCommand and
+ *      SendXWPShellCommand:
+ *
+ *      --  none.
+ *
+ *@@added V1.0.1 (2003-01-05) [umoeller]
+ */
+
+APIRET xsecQueryStatus(PXWPSECSTATUS pStatus)        // out: ring-0 status (ptr can be null)
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_QUERYSTATUS,
+                                      &pCommand)))
+    {
+        if (!(arc = SendXWPShellCommand(pCommand)))
+            if (pStatus)
+                memcpy(pStatus,
+                       &pCommand->pShared->Data.Status,
+                       sizeof(pCommand->pShared->Data.Status));
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ xsecQueryLocalUser:
+ *      returns info for the user who's currently
+ *      logged on locally, that is, the user who
+ *      owns the shell.
+ *
+ *      Required authority: None.
+ *
+ *      Command-specific error codes in addition to those
+ *      returned by CreateXWPShellCommand and
+ *      SendXWPShellCommand:
+ *
+ *      --  XWPSEC_NO_LOCAL_USER: no user is currently
+ *          logged on locally (XWPShell is probably
+ *          displaying logon dialog).
+ *
+ *@@added V0.9.11 (2001-04-22) [umoeller]
+ */
+
+APIRET xsecQueryLocalUser(PXWPUSERDBENTRY *ppLocalUser)     // out: currently logged on user
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_QUERYLOCALUSER,
+                                      &pCommand)))
+    {
+        if (!(arc = SendXWPShellCommand(pCommand)))
+        {
+            // alright:
+            PXWPUSERDBENTRY pLocal = pCommand->pShared->Data.pLocalUser;
+            if (!(*ppLocalUser = (PXWPUSERDBENTRY)malloc(pLocal->cbStruct)))
+                arc = ERROR_NOT_ENOUGH_MEMORY;
+            else
+                memcpy(*ppLocalUser,
+                       pLocal,
+                       pLocal->cbStruct);
+        }
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ xsecQueryAllUsers:
+ *      returns an array of XWPUSERDBENTRY structs
+ *      with all the users currently in the userdb.
+ *
+ *      Warning: The array items are variable in
+ *      size depending on group memberships, so
+ *      always use the cbStruct member of each
+ *      array item to climb to the next.
+ *
+ *      Required authority: XWPPERM_QUERYUSERINFO.
+ *
+ *      Command-specific error codes in addition to those
+ *      returned by CreateXWPShellCommand and
+ *      SendXWPShellCommand:
+ *
+ *      --  XWPSEC_INSUFFICIENT_AUTHORITY
+ *
+ *      plus the error codes from sudbQueryUsers.
+ *
+ *@@added V0.9.19 (2002-04-02) [umoeller]
+ */
+
+APIRET xsecQueryAllUsers(PULONG pcUsers,               // ou: array item count
+                         PXWPUSERDBENTRY *ppaUsers)    // out: array of users (to be freed)
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_QUERYUSERS,
+                                      &pCommand)))
+    {
+        if (!(arc = SendXWPShellCommand(pCommand)))
+        {
+            // alright:
+            // the array items are variable in size, so check
+            ULONG   ul,
+                    cbTotal = 0;
+            PBYTE   pbUserThis = (PBYTE)pCommand->pShared->Data.QueryUsers.paUsers;
+            PXWPUSERDBENTRY paUsers;
+            for (ul = 0;
+                 ul < pCommand->pShared->Data.QueryUsers.cUsers;
+                 ++ul)
+            {
+                ULONG cbThis = ((PXWPUSERDBENTRY)pbUserThis)->cbStruct;
+                cbTotal += cbThis;
+                pbUserThis += cbThis;
+            }
+
+            if (!(paUsers = malloc(cbTotal)))
+                arc = ERROR_NOT_ENOUGH_MEMORY;
+            else
+            {
+                memcpy(paUsers,
+                       pCommand->pShared->Data.QueryUsers.paUsers,
+                       cbTotal);
+                *pcUsers = pCommand->pShared->Data.QueryUsers.cUsers;
+                *ppaUsers = paUsers;
+            }
+
+            // free shared mem given to us by XWPShell
+            DosFreeMem(pCommand->pShared->Data.QueryUsers.paUsers);
+        }
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ xsecQueryGroups:
+ *      returns an array of XWPGROUPDBENTRY structs
+ *      with all the groups currently in the userdb.
+ *
+ *      Required authority: XWPPERM_QUERYUSERINFO.
+ *
+ *      Command-specific error codes in addition to those
+ *      returned by CreateXWPShellCommand and
+ *      SendXWPShellCommand:
+ *
+ *      --  XWPSEC_INSUFFICIENT_AUTHORITY
+ *
+ *      plus the error codes from sudbQueryGroups.
+ *
+ *@@added V0.9.19 (2002-04-02) [umoeller]
+ */
+
+APIRET xsecQueryGroups(PULONG pcGroups,
+                       PXWPGROUPDBENTRY *ppaGroups)    // out: array of users (to be freed)
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_QUERYGROUPS,
+                                      &pCommand)))
+    {
+        if (!(arc = SendXWPShellCommand(pCommand)))
+        {
+            // alright:
+            PXWPGROUPDBENTRY paGroups;
+            ULONG cb =   pCommand->pShared->Data.QueryGroups.cGroups
+                       * sizeof(XWPGROUPDBENTRY);
+
+            if (!(paGroups = malloc(cb)))
+                arc = ERROR_NOT_ENOUGH_MEMORY;
+            else
+            {
+                memcpy(paGroups,
+                       pCommand->pShared->Data.QueryGroups.paGroups,
+                       cb);
+                *pcGroups = pCommand->pShared->Data.QueryGroups.cGroups;
+                *ppaGroups = paGroups;
+            }
+
+            // free shared mem given to us by XWPShell
+            DosFreeMem(pCommand->pShared->Data.QueryGroups.paGroups);
+        }
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ xsecQueryUserName:
+ *      convenience function that returns a single user name.
+ *
+ *      Required authority: none. We consider plain user names
+ *      something not worth protecting.
+ *
+ *@@added V1.0.2 (2003-11-13) [umoeller]
+ */
+
+APIRET xsecQueryUserName(XWPSECID uid,      // in: user ID
+                         PSZ pszUserName)   // out: user name (buffer must be XWPSEC_NAMELEN in size)
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_QUERYUSERNAME,
+                                      &pCommand)))
+    {
+        PQUEUEUNION pUnion = &pCommand->pShared->Data;
+
+        pUnion->QueryUserName.uid = uid;
+
+        if (!(arc = SendXWPShellCommand(pCommand)))
+            strlcpy(pszUserName,
+                    pUnion->QueryUserName.szUserName,
+                    XWPSEC_NAMELEN);
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ xsecQueryProcessOwner:
+ *      returns the UID on whose behalf the given
+ *      process is running.
+ *
+ *      Required authority: none.
+ *
+ *      Command-specific error codes in addition to those
+ *      returned by CreateXWPShellCommand and
+ *      SendXWPShellCommand:
+ *
+ *      --  XWPSEC_INSUFFICIENT_AUTHORITY
+ *
+ *@@added V0.9.19 (2002-04-02) [umoeller]
+ */
+
+APIRET xsecQueryProcessOwner(USHORT pid,            // in: process ID or 0 for caller
+                             XWPSECID *puid)        // out: user ID
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_QUERYPROCESSOWNER,
+                                      &pCommand)))
+    {
+        PQUEUEUNION pUnion = &pCommand->pShared->Data;
+
+        pUnion->QueryProcessOwner.pid = pid;
+
+        if (!(arc = SendXWPShellCommand(pCommand)))
+            *puid = pUnion->QueryProcessOwner.uid;
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ xsecCreateUser:
+ *      creates a new user in the user database.
+ *
+ *      Required authority: XWPPERM_CREATEUSER.
+ *
+ *      Command-specific error codes in addition to those
+ *      returned by CreateXWPShellCommand and
+ *      SendXWPShellCommand:
+ *
+ *      --  ERROR_INVALID_PARAMETER
+ *
+ *      --  ERROR_FILENAME_EXCED_RANGE: one of the given
+ *          strings is too long.
+ *
+ *      --  XWPSEC_INSUFFICIENT_AUTHORITY
+ *
+ *      --  XWPSEC_USER_EXISTS
+ *
+ *@@added V0.9.19 (2002-04-02) [umoeller]
+ */
+
+APIRET xsecCreateUser(PCSZ pcszUserName,        // in: user name
+                      PCSZ pcszFullName,        // in: user's descriptive name
+                      PCSZ pcszPassword,        // in: user's password
+                      XWPSECID gid,             // in: group of the new user
+                      XWPSECID *puid)           // out: user ID that was created
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_CREATEUSER,
+                                      &pCommand)))
+    {
+        PQUEUEUNION pUnion = &pCommand->pShared->Data;
+
+        if (    (!(arc = strhCopyBuf(pUnion->CreateUser.szUserName,
+                                     pcszUserName,
+                                     sizeof(pUnion->CreateUser.szUserName))))
+             && (!(arc = strhCopyBuf(pUnion->CreateUser.szFullName,
+                                     pcszFullName,
+                                     sizeof(pUnion->CreateUser.szFullName))))
+             && (!(arc = strhCopyBuf(pUnion->CreateUser.szPassword,
+                                     pcszPassword,
+                                     sizeof(pUnion->CreateUser.szPassword))))
+             && (!(arc = SendXWPShellCommand(pCommand)))
+           )
+        {
+            // alright:
+            *puid = pUnion->CreateUser.uidCreated;
+        }
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ xsecSetUserData:
+ *      updates the user data for the given UID.
+ *
+ *      Required authority: XWPPERM_CHANGEUSER.
+ *
+ *      Command-specific error codes in addition to those
+ *      returned by CreateXWPShellCommand and
+ *      SendXWPShellCommand:
+ *
+ *      --  ERROR_INVALID_PARAMETER
+ *
+ *      --  ERROR_FILENAME_EXCED_RANGE: one of the given
+ *          strings is too long.
+ *
+ *      --  XWPSEC_INSUFFICIENT_AUTHORITY
+ *
+ *@@added V1.0.1 (2003-01-05) [umoeller]
+ */
+
+APIRET xsecSetUserData(XWPSECID uid,
+                       PCSZ pcszUserName,
+                       PCSZ pcszFullName)
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_SETUSERDATA,
+                                      &pCommand)))
+    {
+        PQUEUEUNION pUnion = &pCommand->pShared->Data;
+
+        pUnion->SetUserData.uid = uid;
+
+        if (    (!(arc = strhCopyBuf(pUnion->SetUserData.szUserName,
+                                     pcszUserName,
+                                     sizeof(pUnion->SetUserData.szUserName))))
+             && (!(arc = strhCopyBuf(pUnion->SetUserData.szFullName,
+                                     pcszFullName,
+                                     sizeof(pUnion->SetUserData.szFullName))))
+             && (!(arc = SendXWPShellCommand(pCommand)))
+           )
+        {
+        }
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ xsecDeleteUser:
+ *      deletes the user account for the given UID.
+ *
+ *      Required authority: XWPPERM_DELETEUSER.
+ *
+ *      Command-specific error codes in addition to those
+ *      returned by CreateXWPShellCommand and
+ *      SendXWPShellCommand:
+ *
+ *      --  XWPSEC_INSUFFICIENT_AUTHORITY
+ *
+ *@@added V1.0.1 (2003-01-05) [umoeller]
+ */
+
+APIRET xsecDeleteUser(XWPSECID uid)
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_DELETEUSER,
+                                      &pCommand)))
+    {
+        PQUEUEUNION pUnion = &pCommand->pShared->Data;
+
+        pUnion->uidDelete = uid;
+
+        arc = SendXWPShellCommand(pCommand);
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ xsecQueryPermissions:
+ *      returns the permissions that the user on whose
+ *      behalf the calling process runs has on the
+ *      given resource. *pflAccess receives the ORed
+ *      XWPACCESS_* flags that represent these
+ *      permissions.
+ *
+ *      This performs a full authorization on the given
+ *      resource according to the general authorization
+ *      rules.
+ *
+ *      Required authority: none.
+ *
+ *      Command-specific error codes in addition to those
+ *      returned by CreateXWPShellCommand and
+ *      SendXWPShellCommand:
+ *
+ *      --  ERROR_INVALID_PARAMETER
+ *
+ *      --  ERROR_FILENAME_EXCED_RANGE
+ *
+ *@@added V1.0.1 (2003-01-10) [umoeller]
+ */
+
+APIRET xsecQueryPermissions(PCSZ pcszFilename,
+                            PULONG pflAccess)
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_QUERYPERMISSIONS,
+                                      &pCommand)))
+    {
+        PQUEUEUNION pUnion = &pCommand->pShared->Data;
+
+        if (    (!(arc = strhCopyBuf(pUnion->QueryPermissions.szResource,
+                                     pcszFilename,
+                                     sizeof(pUnion->QueryPermissions.szResource))))
+             && (!(arc = SendXWPShellCommand(pCommand)))
+           )
+        {
+            *pflAccess = pUnion->QueryPermissions.flAccess;
+        }
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
+
+/*
+ *@@ xsecSwitchUser:
+ *      switches the credentials for the current (calling)
+ *      process. This allows a process to switch to a different
+ *      user, for example to implement a "su" command, since
+ *      subprocesses started after the switch will inherit the
+ *      new credentials.
+ *
+ *      Note that switching the user does not make the new
+ *      user a "local" user. Neither will switching users
+ *      adjust the environment of the current process; to
+ *      implement shell-like behavior, the caller must adjust
+ *      the environment variables such as "USER" and "HOME"
+ *      by itself.
+ *
+ *      Required authority: none.
+ *
+ *      Command-specific error codes in addition to those
+ *      returned by CreateXWPShellCommand and
+ *      SendXWPShellCommand:
+ *
+ *      --  ERROR_INVALID_PARAMETER
+ *
+ *      --  ERROR_FILENAME_EXCED_RANGE
+ *
+ *      --  XWPSEC_NOT_AUTHENTICATED: pUserInfo->szUserName
+ *          is unknown, or pUserInfo->szPassword doesn't
+ *          match the entry in the userdb.
+ *
+ *      If NO_ERROR is returned, the credentials of the current
+ *      process have been switched.
+ */
+
+APIRET xsecSwitchUser(PCSZ pcszUserName,
+                      PCSZ pcszPassword)
+{
+    APIRET              arc = NO_ERROR;
+    PXWPSHELLCOMMAND    pCommand;
+
+    if (!(arc = CreateXWPShellCommand(QUECMD_SWITCHUSER,
+                                      &pCommand)))
+    {
+        PQUEUEUNION pUnion = &pCommand->pShared->Data;
+
+        if (    (!(arc = strhCopyBuf(pUnion->SwitchUser.szUserName,
+                                     pcszUserName,
+                                     sizeof(pUnion->SwitchUser.szUserName))))
+             && (!(arc = strhCopyBuf(pUnion->SwitchUser.szPassword,
+                                     pcszPassword,
+                                     sizeof(pUnion->SwitchUser.szPassword))))
+             && (!(arc = SendXWPShellCommand(pCommand)))
+           )
+        {
+
+        }
+
+        FreeXWPShellCommand(pCommand);
+    }
+
+    return arc;
+}
