/* ********************************************************************* */
/* *                                                                   * */
/* * Copyright (c) 2010 - Dipl.-Ing. Dirk Krause                       * */
/* *                                                                   * */
/* * All rights reserved.                                              * */
/* *                                                                   * */
/* * Redistribution and use in source and binary forms,                * */
/* * with or without modification, are permitted provided              * */
/* * that the following conditions are met:                            * */
/* *                                                                   * */
/* * * Redistributions of source code must retain the above            * */
/* *   copyright notice, this list of conditions and the               * */
/* *   following disclaimer.                                           * */
/* * * Redistributions in binary form must reproduce the above         * */
/* *   opyright notice, this list of conditions and the following      * */
/* *   disclaimer in the documentation and/or other materials          * */
/* *   provided with the distribution.                                 * */
/* * * Neither the name of Dirk Krause nor the names of                * */
/* *   contributors may be used to endorse or promote                  * */
/* *   products derived from this software without specific            * */
/* *   prior written permission.                                       * */
/* *                                                                   * */
/* * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND            * */
/* * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,       * */
/* * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF          * */
/* * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          * */
/* * DISCLAIMED.                                                       * */
/* * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE          * */
/* * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,             * */
/* * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT           * */
/* * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;          * */
/* * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)          * */
/* * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN         * */
/* * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE         * */
/* * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS           * */
/* * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH              * */
/* * DAMAGE.                                                           * */
/* *                                                                   * */
/* ********************************************************************* */

/**	@file	kwt3.ctr	Support functions for kwintool, module 3.
*/


#include "kwintool.h"




#line 49 "kwt3.ctr"



/**	Strings used by the module.
*/
static WCHAR const * const kw[] = {
  /*   0 */ L".exe",
  /*   1 */ L".cfg",
  /*   2 */ L"SOFTWARE\\DKrause\\Shared\\kWinTool",
  /*   3 */ L"SOFTWARE\\DKrause\\Shared\\kWinDown",
  /*   4 */ L"LastUser",
  /*   5 */ L"\\*",
  /*   6 */ L".",
  /*   7 */ L"..",
  /*   8 */ L"ShutdownTimestamp",
  /*   9 */ L".",
  /*  10 */ L" ",
  /*  11 */ L"-",
  /*  12 */ L"----",
  /*  13 */ L"NoCleanup",
  /*  14 */ L"Syntax error, profile base directory already defined!",
  /*  15 */ L"In file \"%ls\"\n",
  /*  16 */ L"Not enough memory!",
  /*  17 */ L"Syntax error, missing profile base directory name!",
  /*  18 */ L"Syntax error!",
  /*  19 */ L"Syntax error, redefinition of interactive role!",
};



static
void
error(size_t in, WCHAR const *fn, unsigned long li)
{
  static BOOL fileShown = FALSE;
  WCHAR const *lbs = NULL;
  WCHAR const *ptr;
  if((fn) && (li)) {
    ptr = fn; lbs = NULL;
    while(*ptr) {
      if(*ptr == L'\\') lbs = ptr;
      ptr++;
    }
    if(lbs) { lbs++; } else { lbs = fn; }
    if(!fileShown) {
      fileShown = TRUE;
      fwprintf(
        stderr,
	kw[15], fn
      );
    }
    fwprintf(
      stderr,
      L"%ls:%lu: %ls\n", lbs, li, kw[in]
    );
  } else {
    fwprintf(
      stderr,
      L"%ls\n", kw[in]
    );
  }
}


/**	Section names in configuration file.
*/
static WCHAR const * const sectionNames[] = {
  L"profiles",
  L"public",
  L"options",
  NULL
};


/**	Option names.
*/
static WCHAR const * const optionNames[] = {
  /*  0 */	L"shutdown.boot",
  /*  1 */	L"shutdown.logout",
  /*  2 */	L"role.interactive",
  NULL
};


/**	Parent of the profile directories.
*/
static WCHAR const *profileBase = NULL;

/**	Name of the interactive role.
*/
static WCHAR const *roleInteractive = NULL;

/**	List of profile directories to keep.
*/
static OLEL *profiles = NULL;


/**	List of public writable directories.
*/
static OLEL *publicdirs = NULL;

/**	Seconds between boot and automatic shutdown.
*/
static unsigned long shutdownAfterBoot = 0UL;

/**	Seconds between logout and automatic shutdown.
*/
static unsigned long shutdownAfterLogout = 0UL;


/**	States while reading configuration file.
*/
typedef enum {
  SECTION_UNKNOWN,	/**< State not set yet. */
  SECTION_PROFILES,	/**< Profiles section. */
  SECTION_PUBLIC,	/**< Public directories section. */
  SECTION_OPTIONS	/**< Options section. */
} KWINTOOL_SECTION;



/**	Read configuration file.
	@param	fipo	File to read.
	@param	cfgn	File name (for diagnostic messages).
	@return	TRUE on success, FALSE on errors.
*/
static BOOL
read_config_file(FILE *fipo, WCHAR const *cfgn)
{
  BOOL back = TRUE;
  BOOL cc = TRUE;
  BOOL lineok;
  KWINTOOL_SECTION st;
  unsigned long lineno = 0UL;
  WCHAR il[KWINTOOL_BUFFER_SIZE], *p1, *p2;
  OLEL *ne;
  unsigned long ul;
  st = SECTION_UNKNOWN;
  while(cc) {
    cc = FALSE; 
    if(fgetws(il, SIZEOF(il,WCHAR)-1, fipo)) {
      lineok = FALSE;
      lineno++;
      p1 = wstr_start(il);
      if(p1) {
        if(*p1 != L'#') {
	  wstr_chomp(p1);
	  if(*p1 == L'[') {		/* New section */
	    st = SECTION_UNKNOWN;
	    p2 = wcschr(il, L']');
	    if(p2) {
	      *p2 = L'\0';
	      p1++;
	      p1 = wstr_start(p1);
	      if(p1) {
	        p2 = wstr_next(p1);
		switch(wstr_array_index(sectionNames, p1)) {
		  case 0: {
		    if(p2) {
		      if(profileBase) {
			error(14, cfgn, lineno);
		      } else {
		        profileBase = wstr_dup(p2);
			if(profileBase) {
			  st = SECTION_PROFILES; lineok = TRUE;
			} else {
			  error(16, cfgn, lineno);
			}
		      }
		    } else {
		      error(17, cfgn, lineno);
		    }
		  } break;
		  case 1: {
		    if(p2) {
		      error(18, cfgn, lineno);
		    } else {
		      st = SECTION_PUBLIC; lineok = TRUE;
		    }
		  } break;
		  case 2: {
		    if(p2) {
		      error(18, cfgn, lineno);
		    } else {
		      st = SECTION_OPTIONS; lineok = TRUE;
		    }
		  } break;
		  default: {
		    error(18, cfgn, lineno);
		  } break;
		}
	      } else {
		error(18, cfgn, lineno);
	      }
	    } else {
	      error(18, cfgn, lineno);
	    }
	  } else {			/* Line in section */
	    switch(st) {
	      case SECTION_PROFILES: {
	        ne = kwt_olel_chain_new(p1, profiles);
		if(ne) {
		  profiles = ne; lineok = TRUE;
		} else {
		  error(16, cfgn, lineno);
		}
	      } break;
	      case SECTION_PUBLIC: {
	        ne = kwt_olel_chain_new(p1, publicdirs);
		if(ne) {
		  publicdirs = ne; lineok = TRUE;
		} else {
		  error(16, cfgn, lineno);
		}
	      } break;
	      case SECTION_OPTIONS: {
	        p2 = wcschr(p1, L'=');
		if(p2) {
		  *(p2++) = L'\0';
		  p2 = wstr_start(p2);
		  wstr_chomp(p1);
		  wstr_chomp(p2);
		  switch(wstr_array_index(optionNames, p1)) {
		    case 0: {
		      if(swscanf(p2, L"%lu", &ul) == 1) {
		        shutdownAfterBoot = ul; lineok = TRUE;
		      }
		    } break;
		    case 1: {
		      if(swscanf(p2, L"%lu", &ul) == 1) {
		        shutdownAfterLogout = ul; lineok = TRUE;
		      }
		    } break;
		    case 2: {
		      if(roleInteractive) {
			error(19, cfgn, lineno);
		      } else {
		        roleInteractive = wstr_dup(p2); lineok = TRUE;
		      }
		    } break;
		    default: {
		      error(18, cfgn, lineno);
		    } break;
		  }
		}
	      } break;
	    }
	  }
	} else { lineok = TRUE; }
      } else { lineok = TRUE; }
      cc = TRUE;
    }
    if(!lineok) { back = FALSE; }
  }
  return back;
}



/**	Set up data structures, read configuration file.
	@return	TRUE on success, FALSE on error.
*/
static BOOL
setup(void)
{
  BOOL back = FALSE;
  WCHAR	cfgn[ _MAX_PATH ];
  size_t s1, s2;
  FILE *fipo;
  if(GetModuleFileName(GetModuleHandle(NULL), cfgn, SIZEOF(cfgn,WCHAR))) {
    s1 = wcslen(cfgn);
    s2 = wcslen(kw[0]);
    if(s1 > s2) {
      if((s1 - s2 + wcslen(kw[1])) < SIZEOF(cfgn,WCHAR)) {
        wcscpy(&(cfgn[s1 - s2]), kw[1]);
	fipo = _wfopen(cfgn, L"r");
	if(fipo) {
	  back = read_config_file(fipo, cfgn);
#if KWINTOOL_DEBUG
	  fwprintf(stderr, L"DEBUG: Config %d\n", (int)back);
#endif
	  fclose(fipo);
	}
      }
    }
  }
  return back;
}



/**	Clean up data structures.
*/
static void
cleanup(void)
{
#if KWINTOOL_DEBUG
  OLEL *olel;
#endif
  if(profileBase) {
#if KWINTOOL_DEBUG
    fwprintf(stderr, L"DEBUG: Profile base = \"%ls\"\n", profileBase);
#endif
    kwt_free((void *)profileBase);
    profileBase  = NULL;
  }
  if(roleInteractive) {
    kwt_free((void *)roleInteractive);
    roleInteractive = NULL;
  }
  if(profiles) {
#if KWINTOOL_DEBUG
    olel = profiles;
    while(olel) {
      if(olel->name) {
        fwprintf(stderr, L"DEBUG: Profile \"%ls\"\n", olel->name);
      }
      olel = olel->next;
    }
#endif
    kwt_olel_chain_delete(profiles);
    profiles = NULL;
  }
  if(publicdirs) {
#if KWINTOOL_DEBUG
    olel = publicdirs;
    while(olel) {
      if(olel->name) {
        fwprintf(stderr, L"DEBUG: Public directory \"%ls\"\n", olel->name);
      }
      olel = olel->next;
    }
#endif
    kwt_olel_chain_delete(publicdirs);
    publicdirs = NULL;
  }
}


/**	Get string entry from registry.
	@param	kn	Key name.
	@param	en	Entry name.
	@param	rb	Result buffer.
	@param	sz	Size of \a rb in WCHAR.
	@return	TRUE on success, FALSE on error.
*/
static BOOL
get_reg_string(WCHAR const *kn, WCHAR const *en, WCHAR *rb, size_t sz)
{
  BOOL back = FALSE;
  LONG res;
  HKEY hk;
  DWORD dwType;
  DWORD dwSz;
  res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, kn, (DWORD)0, KEY_READ, &hk);
  if(ERROR_SUCCESS == res) {
    dwType = REG_SZ; dwSz = sz * sizeof(WCHAR);
    res = RegQueryValueEx(hk, en, NULL, &dwType, (LPBYTE)rb, &dwSz);
    if(ERROR_SUCCESS == res) {
      if((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ)) {
        if(dwSz > 0) {
	  dwSz = dwSz / sizeof(WCHAR);
	  if(dwSz > 0) {
	    if(dwSz < sz) {
	      rb[dwSz] = L'\0';
	    }
	    rb[sz - 1] = L'\0';
	    back = TRUE;
	  }
	}
      }
    }
    RegCloseKey(hk);
  }
  return back;
}


/**	Set string entry in registry.
	@param	kn	Key name.
	@param	en	Entry name.
	@param	va	Entry value.
	@return	TRUE on success, FALSE on error.
*/
static BOOL
set_reg_string(WCHAR const *kn, WCHAR const *en, WCHAR *va)
{
  BOOL back = FALSE;
  LONG res;
  HKEY hk;
  DWORD dwSz;
  res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, kn, (DWORD)0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hk, NULL);
  if(ERROR_SUCCESS == res) {
    dwSz = (1 + wcslen(va)) * sizeof(WCHAR);
    res = RegSetValueEx(hk, en, (DWORD)0, REG_SZ, (const BYTE *)va, dwSz);
    if(ERROR_SUCCESS == res) {
      back = TRUE;
    }
    RegCloseKey(hk);
  }
  return back;
}


/**	Add names of existing users to list of profile to save.
	@return	TRUE on success, FALSE on error.
*/
static BOOL
add_profiles_for_users(void)
{
  BOOL back = TRUE;
  WCHAR lu[KWINTOOL_USER_NAME_SIZE];
  OLEL *ne;
  LPUSER_INFO_0 pBuf = NULL;
  LPUSER_INFO_0 pTmpBuf;
  DWORD dwLevel = 0;
  DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH;
  DWORD dwEntriesRead = 0;
  DWORD dwTotalEntries = 0;
  DWORD dwResumeHandle = 0;
  DWORD i;
  DWORD dwTotalCount = 0;
  NET_API_STATUS nStatus;
  LPTSTR pszServerName = L"\\\\pckr";
  /* Add name of last user if available.
 */
  if(get_reg_string(kw[2], kw[4], lu, SIZEOF(lu,WCHAR))) {
    ne = kwt_olel_chain_new(lu, profiles);
    if(ne) {
      profiles = ne;
    } else {
      back = FALSE;
    }
  }
  /* Add directories for user known on the system.
 */
  do {
    pBuf = NULL;
    nStatus = NetUserEnum(
      pszServerName, dwLevel, FILTER_NORMAL_ACCOUNT, (LPBYTE *)(&pBuf),
      dwPrefMaxLen, &dwEntriesRead, &dwTotalEntries, &dwResumeHandle
    );
    if((nStatus == NERR_Success) || (nStatus == ERROR_MORE_DATA)) {
      if((pTmpBuf = pBuf) != NULL) {
        for(i = 0; i < dwEntriesRead; i++) {
	  if(pTmpBuf != NULL) {
	    if(pTmpBuf->usri0_name) {
#if KWINTOOL_DEBUG
	      fwprintf(stderr, L"DEBUG: Name      \"%ls\"\n", pTmpBuf->usri0_name);
#endif
	      if(wcslen(pTmpBuf->usri0_name) > 0) {
	        ne = kwt_olel_chain_new(pTmpBuf->usri0_name, profiles);
		if(ne) {
		  profiles = ne;
		} else {
		  back = FALSE;
		}
	      }
	    }
	    pTmpBuf++;
	    dwTotalCount++;
	  }
	}
      }
    }
    if(pBuf != NULL) { NetApiBufferFree(pBuf); pBuf = NULL; }
  } while(nStatus == ERROR_MORE_DATA);
  if(pBuf != NULL) { NetApiBufferFree(pBuf); }
  return back;
}


/**	Remove unnecessary profile directories.
*/
static void
remove_profile_directories(void)
{
  WCHAR pa[ _MAX_PATH ];
  struct _wfinddata64_t fileinfo;
  intptr_t x;
  OLEL *ne;

  if(profileBase) {
    if(add_profiles_for_users()) {
      if((wcslen(profileBase) + wcslen(kw[5])) < SIZEOF(pa,WCHAR)) {
        wcscpy(pa, profileBase);
	wcscat(pa, kw[5]);
	x = _wfindfirst64(pa, &fileinfo);
	if(x != -1) {
	  do {
	    if(wcscmp(fileinfo.name, kw[6])) {
	      if(wcscmp(fileinfo.name, kw[7])) {
	        ne = kwt_olel_chain_find(profiles, fileinfo.name, 0);
		if(!(ne)) {
		  if(fileinfo.attrib & _A_SUBDIR) {
		    fwprintf(
		      stdout,
		      L"RD  /S /Q    \"%ls\\%ls\"\n",
		      profileBase,
		      fileinfo.name
		    );
		  } else {
		    fwprintf(
		      stdout,
		      L"DEL /F /S /Q \"%ls\\%ls\"\n",
		      profileBase,
		      fileinfo.name
		    );
		  }
		}
	      }
	    }
	  } while(_wfindnext64(x, &fileinfo) == 0);
	  _findclose(x);
	}
      }
    }
  }
}


/**	Clean up one directory.
	@param	dn	Directory name.
*/
static void
cleanup_directory(WCHAR const *dn)
{
  intptr_t x;
  struct _wfinddata64_t fileinfo;
  WCHAR pa[ _MAX_PATH ];
  if((wcslen(dn) + wcslen(kw[5])) < SIZEOF(pa,WCHAR)) {
    wcscpy(pa, dn);
    wcscat(pa, kw[5]);
    x = _wfindfirst64(pa, &fileinfo);
    if(x != -1) {
      do {
        if(wcscmp(fileinfo.name, kw[6])) {
	  if(wcscmp(fileinfo.name, kw[7])) {
	    if(fileinfo.attrib & _A_SUBDIR) {
	      fwprintf(stdout, L"RD  /S /Q    \"%ls\\%ls\"\n", dn, fileinfo.name);
	    } else {
	      fwprintf(stdout, L"DEL /F /S /Q \"%ls\\%ls\"\n", dn, fileinfo.name);
	    }
	  }
	}
      } while(_wfindnext64(x, &fileinfo) == 0);
      _findclose(x);
    }
  }
}


/**	Clean up all public directories.
*/
static void
clean_public(void)
{
  OLEL *ne;
  ne = publicdirs;
  while(ne) {
    cleanup_directory(ne->name);
    ne = ne->next;
  }
}


/**	Set permissions to directory to allow later cleanup by System.
	@param	dn	Directory name.
	@param	isPublic	Flag: 1=public directory, 0=user profile.
*/
static void
perm_directory(WCHAR const *dn, int isPublic)
{
  fwprintf(stdout, L"CACLS \"%ls\" /T /E /C /G SYSTEM:F\n", dn);
  fwprintf(stdout, L"CACLS \"%ls\" /T /E /C /G Administrator:F\n", dn);
  if((roleInteractive) && (isPublic)) {
    fwprintf(stdout, L"CACLS \"%ls\" /T /E /C /G %ls:F\n", dn, roleInteractive);
  }
}


/**	Set permissions to public directories to allow later clean up.
*/
static void
perm_public(void)
{
  OLEL *ne;
  ne = publicdirs;
  while(ne) {
    perm_directory(ne->name, 1);
    ne = ne->next;
  }
}


/**	Set permissions to profile directories to allow later clean up.
*/
static void
perm_profile(void)
{
  WCHAR *up;
  up = _wgetenv(L"USERPROFILE");
  if(up) {
    perm_directory(up, 0);
  }
}


/**	Save name of current user to the registry.
*/
static void
save_current_user(void)
{
  WCHAR un[KWINTOOL_USER_NAME_SIZE];
  WCHAR *myun = NULL;
  DWORD szun;
  szun = SIZEOF(un,WCHAR);
  if(GetUserName(un, &szun)) {
    if(szun > 0) {
      myun = un;
    }
  }
  if(!(myun)) {
    myun = _wgetenv(L"USERNAME");
  }
  if(myun) {
    set_reg_string(kw[2], kw[4], myun);
  }
}


/**	Set a new shutdown timestamp.
	@param	timer	New shutdown timestamp.
*/
static void
set_shutdown_time(__time64_t timer)
{
  LONG res;
  HKEY hk;
  DWORD dwSz;
  res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, kw[3], (DWORD)0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hk, NULL);
  if(ERROR_SUCCESS == res) {
    dwSz = sizeof(timer);
    res = RegSetValueEx(hk, kw[8], (DWORD)0, REG_QWORD, (const BYTE *)(&timer), dwSz);
    RegCloseKey(hk);
  }
}



/**	Set or remove shutdown timestamp.
	@param	cr	Criteria: 0=login, no shutdown, 1=logout, 2=boot.
*/
static void
shutdown_time(int cr)
{
  __time64_t timer;
  switch(cr) {
    case 2: {
      _time64(&timer);
      timer = timer + (__time64_t)shutdownAfterBoot;
      set_shutdown_time(timer);
    } break;
    case 1: {
      _time64(&timer);
      timer = timer + (__time64_t)shutdownAfterLogout;
      set_shutdown_time(timer);
    } break;
    default: {
      set_shutdown_time((__time64_t)(0UL));
    } break;
  }
}


/**	Check whether we are allowed to clean up.
	@param	reset	Flag: Reset disable flag.
	@return	TRUE if cleanup is allowed, FALSE if cleanup is denied.
*/
static
BOOL check_cleanup(BOOL reset)
{
  BOOL back = TRUE;
  LONG res;
  HKEY hk;
  DWORD dwSz;
  DWORD dwType;
  DWORD dwData;
  res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, kw[2], (DWORD)0, NULL, REG_OPTION_NON_VOLATILE, (reset ? KEY_ALL_ACCESS : KEY_READ), NULL, &hk, NULL);
  if(ERROR_SUCCESS == res) {
    dwType = REG_DWORD; dwSz = sizeof(dwData);
    res = RegQueryValueEx(hk, kw[13], NULL, &dwType, (LPBYTE)(&dwData), &dwSz);
    if(ERROR_SUCCESS == res) {
        if(dwType  == REG_DWORD) {
	  if(dwData) {
	    back = FALSE;
	  }
	}
    }
    if(reset) {
      if(!back) {
        RegDeleteValue(hk, kw[13]);
      }
    }
    RegCloseKey(hk);
  }
  return back;
}



/**	Actions on boot.
*/
static void
on_boot(void)
{
  if(check_cleanup(FALSE)) {
    /* Remove user profiles
 */
    remove_profile_directories();
    /* Clean up public directories
 */
    clean_public();
  }
  /* Initialize automatic shutdown
 */
  shutdown_time(2);
}


/**	Actions on login.
*/
static void
on_login(void)
{
  /* Set registry entry to avoid shutdown.
 */
  shutdown_time(0);
  if(check_cleanup(TRUE)) {
    /* Clean up public directories
 */
    clean_public();
  }
  /* Set registry entry for user name.
 */
  save_current_user();
}


/**	Actions on logout.
*/
static void
on_logout(void)
{
  /* Set registry entry for user name.
 */
  save_current_user();
  /* Set permissions to public directories
 */
  perm_public();
  /* Set permissions to profile directory
 */
  perm_profile();
  if(check_cleanup(FALSE)) {
    /* Clean up public directories.
 */
    clean_public();
  }
  /* Set registry entry for automatic shutdown.
 */
  shutdown_time(1);
}


/**	Actions on shutdown.
*/
static void
on_shutdown(void)
{
  /* Set registry entry for user name.
 */
  save_current_user();
  /* Set permissions to public directories
 */
  perm_public();
  if(check_cleanup(FALSE)) {
    /* Clean up public directories
 */
    clean_public();
  }
  /* Set registry entry to avoid shutdown.
 */
  shutdown_time(0);
}


void
kwt_boot_login_logout_shutdown(int stage)
{
  if(setup()) {
    switch(stage) {
      case 0: {
        on_boot();
      } break;
      case 1: {
        on_login();
      } break;
      case 2: {
        on_logout();
      } break;
      case 3: {
        on_shutdown();
      } break;
    }
  }
  cleanup();
}


/**	Run directory listing for a specified directory.
	@param	pa	Buffer  containing the directory name.
	@param	sz	Size of the buffer (must be checked before adding \*).
*/
static void
run_ls(WCHAR *pa, size_t sz)
{
  intptr_t x;
  struct _wfinddata64_t fileinfo;
  WCHAR t[16];
  WCHAR a[16];
  if((wcslen(pa) + wcslen(kw[5])) < sz) {
    wcscat(pa, kw[5]);
    x = _wfindfirst64(pa, &fileinfo);
    if(x != -1) {
      do {
        if(wcscmp(fileinfo.name, kw[6])) {
	  if(wcscmp(fileinfo.name, kw[7])) {
	    wcscpy(t, kw[11]);
	    wcscpy(a, kw[12]);
	    if(fileinfo.attrib & _A_SUBDIR) {
	      t[0] = L'D';
	    }
	    if(fileinfo.attrib & _A_SYSTEM) {
	      a[0] = L'S';
	    }
	    if(fileinfo.attrib & _A_RDONLY) {
	      a[1] = L'R';
	    }
	    if(fileinfo.attrib & _A_HIDDEN) {
	      a[2] = L'H';
	    }
	    if(fileinfo.attrib & _A_ARCH) {
	      a[3] = L'A';
	    }
	    fwprintf(
	      stderr,
	      L"%ls %ls %20I64u %ls\n",
	      t, a, (long long unsigned)(fileinfo.size), fileinfo.name
	    );
	  }
	}
      } while(_wfindnext64(x, &fileinfo) == 0);
      _findclose(x);
    }
  }
}



void
kwt_ls(int argc, WCHAR **argv)
{
  WCHAR pa[ _MAX_PATH ];
  int toolong = 0;
  int i;
  if(argc > 2) {
    pa[0] = L'\0';
    for(i = 2; i < argc; i++) {
       if(i == 2) {
         if(wcslen(argv[i]) < SIZEOF(pa,WCHAR)) {
	   wcscpy(pa, argv[i]);
	 } else {
	   toolong = 1;
	 }
       } else {
         if((wcslen(pa)+wcslen(kw[10])+wcslen(argv[i])) < SIZEOF(pa,WCHAR)) {
	   wcscat(pa, kw[10]);
	   wcscat(pa, argv[i]);
	 } else {
	   toolong = 1;
	 }
       }
    }
    if(!toolong) {
      run_ls(pa, SIZEOF(pa,WCHAR));
    }
  } else {
    wcscpy(pa, kw[9]);
    run_ls(pa, SIZEOF(pa,WCHAR));
  }
}



void
kwt_no_cleanup(void)
{
  LONG res;
  HKEY hk;
  DWORD dwData;
  res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, kw[2], (DWORD)0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hk, NULL
  );
  if(ERROR_SUCCESS == res) {
    dwData = 1;
    res = RegSetValueEx(hk, kw[13], (DWORD)0, REG_DWORD, (const BYTE *)(&dwData), sizeof(DWORD));
    RegCloseKey(hk);
  }
}



void
kwt_yes_cleanup(void)
{
  LONG res;
  HKEY hk;
  res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, kw[2], (DWORD)0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hk, NULL);
  if(ERROR_SUCCESS == res) {
    RegDeleteValue(hk, kw[13]);
    RegCloseKey(hk);
  }
}



