/*
 * cns.c
 *
 * Main routine of the Kerberos user interface.  Also handles
 * all dialog level management functions.
 *
 * Copyright 1994 by the Massachusetts Institute of Technology.
 *
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 */

#include <windows.h>
#include <windowsx.h>

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <malloc.h>
#include <ctype.h>
#include <time.h>

#include "cns.h"
#include "tktlist.h"
#include "cns_reg.h"

#include "../lib/gic.h"

enum {				       /* Actions after login */
  LOGIN_AND_EXIT,
  LOGIN_AND_MINIMIZE,
  LOGIN_AND_RUN,
};

/*
 * Globals
 */
static HICON kwin_icons[MAX_ICONS];    /* Icons depicting time */
HFONT hfontdialog = NULL;	       /* Font in which the dialog is drawn. */
static HFONT hfonticon = NULL;	       /* Font for icon label */
HINSTANCE hinstance;
static int dlgncmdshow;		       /* ncmdshow from WinMain */
#if 0
static UINT wm_kerberos_changed;       /* message for cache changing */
#endif
static int action;		       /* After login actions */
static UINT kwin_timer_id;	       /* Timer being used for update */
BOOL alert;		       	       /* Actions on ticket expiration */
BOOL beep;
static BOOL alerted;		       /* TRUE when user already alerted */
BOOL isblocking = FALSE;	       /* TRUE when blocked in WinSock */
static DWORD blocking_end_time;	       /* Ending count for blocking timeout */
static FARPROC hook_instance;	       /* handle for blocking hook function */

char confname[FILENAME_MAX];           /* krb5.conf (or krb.conf for krb4) */

#ifdef KRB5
char ccname[FILENAME_MAX];             /* ccache file location */
BOOL forwardable;                      /* TRUE to get forwardable tickets */
krb5_context k5_context;
krb5_ccache k5_ccache;
#endif

/*
 * Function: Called during blocking operations.  Implement a timeout
 *	if nothing occurs within the specified time, cancel the blocking
 *	operation.  Also permit the user to press escape in order to
 *	cancel the blocking operation.
 *
 * Returns: TRUE if we got and dispatched a message, FALSE otherwise.
 */
BOOL CALLBACK
blocking_hook_proc(void)
{
  MSG msg;
  BOOL rc;

  if (GetTickCount() > blocking_end_time) {
    WSACancelBlockingCall();
    return FALSE;
  }

  rc = (BOOL)PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
  if (!rc)
    return FALSE;

  if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE) {
    WSACancelBlockingCall();
    blocking_end_time = msg.time - 1;
    return FALSE;
  }

  TranslateMessage(&msg);
  DispatchMessage(&msg);

  return TRUE;
}


/*
 * Function: Set up a blocking hook function.
 *
 * Parameters:
 *	timeout - # of seconds to block for before cancelling.
 */
void
start_blocking_hook(int timeout)
{
  FARPROC proc;

  if (isblocking)
    return;

  isblocking = TRUE;
  blocking_end_time = GetTickCount() + (1000 * timeout);
#ifdef _WIN32
  proc = WSASetBlockingHook(blocking_hook_proc);
#else
  hook_instance = MakeProcInstance(blocking_hook_proc, hinstance);
  proc = WSASetBlockingHook(hook_instance);
#endif
  assert(proc != NULL);
}


/*
 * Function: End the blocking hook fuction set up above.
 */
void
end_blocking_hook(void)
{
#ifndef _WIN32
  FreeProcInstance(hook_instance);
#endif
  WSAUnhookBlockingHook();
  isblocking = FALSE;
}


/*
 * Function: Centers the specified window on the screen.
 *
 * Parameters:
 *		hwnd - the window to center on the screen.
 */
void
center_dialog(HWND hwnd)
{
  int scrwidth, scrheight;
  int dlgwidth, dlgheight;
  RECT r;
  HDC hdc;

  if (hwnd == NULL)
    return;

  GetWindowRect(hwnd, &r);
  dlgwidth = r.right  - r.left;
  dlgheight = r.bottom - r.top ;
  hdc = GetDC(NULL);
  scrwidth = GetDeviceCaps(hdc, HORZRES);
  scrheight = GetDeviceCaps(hdc, VERTRES);
  ReleaseDC(NULL, hdc);
  r.left = (scrwidth - dlgwidth) / 2;
  r.top  = (scrheight - dlgheight) / 2;
  MoveWindow(hwnd, r.left, r.top, dlgwidth, dlgheight, TRUE);
}


/*
 * Function: Positions the kwin dialog either to the saved location
 * 	or the center of the screen if no saved location.
 *
 * Parameters:
 *		hwnd - the window to center on the screen.
 */
static void
position_dialog(HWND hwnd)
{
  int scrwidth, scrheight;
  HDC hdc;
  int x, y, cx, cy;

  if (hwnd == NULL)
    return;

  hdc = GetDC(NULL);
  scrwidth = GetDeviceCaps(hdc, HORZRES);
  scrheight = GetDeviceCaps(hdc, VERTRES);
  ReleaseDC(NULL, hdc);
  x = cns_res.x;
  y = cns_res.y;
  cx = cns_res.cx;
  cy = cns_res.cy;

  if (x > scrwidth ||
      y > scrheight ||
      x + cx <= 0 ||
      y + cy <= 0)
    center_dialog(hwnd);
  else
    MoveWindow(hwnd, x, y, cx, cy, TRUE);
}


/*
 * Function: Set font of all dialog items.
 *
 * Parameters:
 *		hwnd - the dialog to set the font of
 */
void
set_dialog_font(HWND hwnd, HFONT hfont)
{
  hwnd = GetWindow(hwnd, GW_CHILD);

  while (hwnd != NULL) {
    SetWindowFont(hwnd, hfont, 0);
    hwnd = GetWindow(hwnd, GW_HWNDNEXT);
  }
}


/*
 * Function: Trim leading and trailing white space from a string.
 *
 * Parameters:
 *	s - the string to trim.
 */
void
trim(char *s)
{
  int l;
  int i;

  for (i = 0 ; s[i] ; i++)
    if (s[i] != ' ' && s[i] != '\t')
      break;

  l = strlen(&s[i]);
  memmove(s, &s[i], l + 1);

  for (l--; l >= 0; l--) {
    if (s[l] != ' ' && s[l] != '\t')
      break;
  }
  s[l + 1] = 0;
}


/*
 * Function: This routine figures out the current time epoch and
 * returns the conversion factor.  It exists because Microloss
 * screwed the pooch on the time() and _ftime() calls in its release
 * 7.0 libraries.  They changed the epoch to Dec 31, 1899!
 */
time_t
kwin_get_epoch(void)
{
  static struct tm jan_1_70 = {0, 0, 0, 1, 0, 70};
  time_t epoch = 0;

  epoch = -mktime(&jan_1_70);		/* Seconds til 1970 localtime */
  epoch += _timezone;				/* Seconds til 1970 GMT */

  return epoch;
}


/*
 * Function: Save the credentials for later restoration.
 *
 * Parameters:
 *	c - Returned pointer to saved credential cache.
 *
 *	pname - Returned as principal name of session.
 *
 *	pinstance - Returned as principal instance of session.
 *
 *	ncred - Returned number of credentials saved.
 */
static void
push_credentials(CREDENTIALS **cp, char *pname, char *pinstance, int *ncred)
{
#ifdef KRB4
  int i;
  char service[ANAME_SZ];
  char instance[INST_SZ];
  char realm[REALM_SZ];
  CREDENTIALS *c;

  if (krb_get_tf_fullname(NULL, pname, pinstance, NULL) != KSUCCESS) {
    pname[0] = 0;

    pinstance[0] = 0;
  }

  *ncred = krb_get_num_cred();
  if (*ncred <= 0)
    return;

  c= malloc(*ncred * sizeof(CREDENTIALS));
  assert(c != NULL);
  if (c == NULL) {
    *ncred = 0;

    return;
  }

  for (i = 0; i < *ncred; i++) {
    krb_get_nth_cred(service, instance, realm, i + 1);
    krb_get_cred(service, instance, realm, &c[i]);
  }

  *cp = c;
#endif

#ifdef KRB5     /* FIXME */
  return;
#endif
}


/*
 * Function: Restore the saved credentials.
 *
 *	c - Pointer to saved credential cache.
 *
 *	pname - Principal name of session.
 *
 *	pinstance - Principal instance of session.
 *
 *	ncred - Number of credentials saved.
 */
static void
pop_credentials(CREDENTIALS *c, char *pname, char *pinstance, int ncred)
{
#ifdef KRB4
  int i;

  if (pname[0])
    in_tkt(pname, pinstance);
  else
    dest_tkt();

  if (ncred <= 0)
    return;

  for (i = 0; i < ncred; i++) {
    krb_save_credentials(c[i].service, c[i].instance, c[i].realm,
			 c[i].session, c[i].lifetime, c[i].kvno,
			 &(c[i].ticket_st),
			 c[i].issue_date);
  }

  free(c);
#endif
#ifdef KRB5     /* FIXME */
  return;
#endif
}


/*
 * Function: Save most recent login triplets for placement on the
 *	bottom of the file menu.
 *
 * Parameters:
 *	hwnd - the handle of the window containing the menu to edit.
 *
 *	name - A login name to save in the recent login list
 *
 *	instance - An instance to save in the recent login list
 *
 *	realm - A realm to save in the recent login list
 */
static void
kwin_push_login(HWND hwnd, char *name, char *instance, char *realm)
{
  HMENU hmenu;
  int i;
  int id;
  int ctitems;
  char fullname[MAX_K_NAME_SZ + 3];
  char menuitem[MAX_K_NAME_SZ + 3];
  BOOL rc;

  fullname[sizeof(fullname) - 1] = '\0';
  strncpy(fullname, "&x ", sizeof(fullname) - 1);
  strncat(fullname, name, sizeof(fullname) - 1 - strlen(fullname));
  strncat(fullname, ".", sizeof(fullname) - 1 - strlen(fullname));
  strncat(fullname, instance, sizeof(fullname) - 1 - strlen(fullname));
  strncat(fullname, "@", sizeof(fullname) - 1 - strlen(fullname));
  strncat(fullname, realm, sizeof(fullname) - 1 - strlen(fullname));

  hmenu = GetMenu(hwnd);
  assert(hmenu != NULL);

  hmenu = GetSubMenu(hmenu, 0);
  assert(hmenu != NULL);

  ctitems = GetMenuItemCount(hmenu);
  assert(ctitems >= FILE_MENU_ITEMS);

  if (ctitems == FILE_MENU_ITEMS) {
    rc = AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);
    assert(rc);

    ctitems++;
  }

  for (i = FILE_MENU_ITEMS + 1; i < ctitems; i++) {
    GetMenuString(hmenu, i, menuitem, sizeof(menuitem), MF_BYPOSITION);

    if (strcmp(&fullname[3], &menuitem[3]) == 0) {
      rc = RemoveMenu(hmenu, i, MF_BYPOSITION);
      assert(rc);

      ctitems--;

      break;
    }
  }

  rc = InsertMenu(hmenu, FILE_MENU_ITEMS + 1, MF_BYPOSITION, 1, fullname);
  assert(rc);

  ctitems++;
  if (ctitems - FILE_MENU_ITEMS - 1 > FILE_MENU_MAX_LOGINS) {
    RemoveMenu(hmenu, ctitems - 1, MF_BYPOSITION);

    ctitems--;
  }

  id = 0;
  for (i = FILE_MENU_ITEMS + 1; i < ctitems; i++) {
    GetMenuString(hmenu, i, menuitem, sizeof(menuitem), MF_BYPOSITION);

    rc = RemoveMenu(hmenu, i, MF_BYPOSITION);
    assert(rc);

    menuitem[1] = '1' + id;
    rc = InsertMenu(hmenu, i, MF_BYPOSITION, IDM_FIRST_LOGIN + id, menuitem);
    assert(rc);

    id++;
  }
}


/*
 * Function: Initialize the logins on the file menu form the KERBEROS.INI
 *	file.
 *
 * Parameters:
 *	hwnd - handle of the dialog containing the file menu.
 */
static void
kwin_init_file_menu(HWND hwnd)
{
  HMENU hmenu;
  int i;
  char menuitem[MAX_K_NAME_SZ + 3];
  int id;
  BOOL rc;

  hmenu = GetMenu(hwnd);
  assert(hmenu != NULL);

  hmenu = GetSubMenu(hmenu, 0);
  assert(hmenu != NULL);

  id = 0;
  for (i = 0; i < FILE_MENU_MAX_LOGINS; i++) {
    strcpy(menuitem + 3, cns_res.logins[i]);

    if (!menuitem[3])
      continue;

    menuitem[0] = '&';
    menuitem[1] = '1' + id;
    menuitem[2] = ' ';

    if (id == 0) {
      rc = AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);
      assert(rc);
    }
    AppendMenu(hmenu, MF_STRING, IDM_FIRST_LOGIN + id, menuitem);

    id++;
  }
}


/*
 * Function: Save the items on the file menu in the KERBEROS.INI file.
 *
 * Parameters:
 *	hwnd - handle of the dialog containing the file menu.
 */
static void
kwin_save_file_menu(HWND hwnd)
{
  HMENU hmenu;
  int i;
  int id;
  int ctitems;
  char menuitem[MAX_K_NAME_SZ + 3];

  hmenu = GetMenu(hwnd);
  assert(hmenu != NULL);

  hmenu = GetSubMenu(hmenu, 0);
  assert(hmenu != NULL);

  ctitems = GetMenuItemCount(hmenu);
  assert(ctitems >= FILE_MENU_ITEMS);

  id = 0;
  for (i = FILE_MENU_ITEMS + 1; i < ctitems; i++) {
    GetMenuString(hmenu, i, menuitem, sizeof(menuitem), MF_BYPOSITION);

    strcpy(cns_res.logins[id], menuitem + 3);

    id++;
  }
}



/*
 * Function: Given an expiration time, choose an appropriate
 *	icon to display.
 *
 * Parameters:
 *	expiration time of expiration in time() compatible units
 *
 * Returns: Handle of icon to display
 */
HICON
kwin_get_icon(time_t expiration)
{
  int ixicon;
  time_t dt;

  dt = expiration - time(NULL);
  dt = dt / 60;			/* convert to minutes */
  if (dt <= 0)
    ixicon = IDI_EXPIRED - IDI_FIRST_CLOCK;
  else if (dt > 60)
    ixicon = IDI_TICKET - IDI_FIRST_CLOCK;
  else
    ixicon = (int)(dt / 5);

  return kwin_icons[ixicon];
}


/*
 * Function: Intialize name fields in the Kerberos dialog.
 *
 * Parameters:
 *	hwnd - the window recieving the message.
 *
 *	fullname - the full kerberos name to initialize with
 */
void
kwin_init_name(HWND hwnd, char *fullname)
{
  char name[ANAME_SZ];
  char instance[INST_SZ];
  char realm[REALM_SZ];
  int krc;
#ifdef KRB5
  krb5_error_code code;
  char *ptr;
#endif    

  if (fullname == NULL || fullname[0] == 0) {
#ifdef KRB4
    strcpy(name, krb_get_default_user());
    strcpy(instance, cns_res.instance);
    krc = krb_get_lrealm(realm, 1);
    if (krc != KSUCCESS)
      realm[0] = 0;
    strcpy(realm, cns_res.realm);
#endif /* KRB4 */

#ifdef KRB5
    strcpy(name, cns_res.name);
    
    *realm = '\0';
    code = krb5_get_default_realm(k5_context, &ptr);
    if (!code) {
      strcpy(realm, ptr);
      /*      free(ptr); XXX */
    }
    strcpy(realm, cns_res.realm);
#endif /* KRB5 */

  } else {
#ifdef KRB4
    kname_parse(name, instance, realm, fullname);
    SetDlgItemText(hwnd, IDD_LOGIN_INSTANCE, instance);
#endif

#ifdef KRB5
    krc = k5_kname_parse(name, realm, fullname);
    *instance = '\0';
#endif
  }

  SetDlgItemText(hwnd, IDD_LOGIN_NAME, name);
  SetDlgItemText(hwnd, IDD_LOGIN_REALM, realm);
}


/*
 * Function: Set the focus to the name control if no name
 * 	exists, the realm control if no realm exists or the
 * 	password control.  Uses PostMessage not SetFocus.
 *
 * Parameters:
 *	hwnd - the Window handle of the parent.
 */
void
kwin_set_default_focus(HWND hwnd)
{
  char name[ANAME_SZ];
  char realm[REALM_SZ];
  HWND hwnditem;

  GetDlgItemText(hwnd, IDD_LOGIN_NAME, name, sizeof(name));

  trim(name);
  if (strlen(name) <= 0)
    hwnditem = GetDlgItem(hwnd, IDD_LOGIN_NAME);
  else {
    GetDlgItemText(hwnd, IDD_LOGIN_REALM, realm, sizeof(realm));
    trim(realm);

    if (strlen(realm) <= 0)
      hwnditem = GetDlgItem(hwnd, IDD_LOGIN_REALM);
    else
      hwnditem = GetDlgItem(hwnd, IDD_LOGIN_PASSWORD);
  }

  PostMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)hwnditem, MAKELONG(1, 0));
}


/*
 * Function: Save the values which live in the KERBEROS.INI file.
 *
 * Parameters:
 *	hwnd - the window handle of the dialog containing fields to
 *		be saved
 */
static void
kwin_save_name(HWND hwnd)
{
  char name[ANAME_SZ];
  char instance[INST_SZ];
  char realm[REALM_SZ];

  GetDlgItemText(hwnd, IDD_LOGIN_NAME, name, sizeof(name));
  trim(name);

#ifdef KRB4
  krb_set_default_user(name);
  GetDlgItemText(hwnd, IDD_LOGIN_INSTANCE, instance, sizeof(instance));
  trim(instance);
  strcpy(cns_res.instance, instance);
#endif

#ifdef KRB5
  strcpy(cns_res.name, name);
  *instance = '\0';
#endif

  GetDlgItemText(hwnd, IDD_LOGIN_REALM, realm, sizeof(realm));
  trim(realm);
  strcpy(cns_res.realm, realm);

  kwin_push_login(hwnd, name, instance, realm);
}


/*
 * Function: Process WM_INITDIALOG messages.  Set the fonts
 *	for all items on the dialog and populate the ticket list.
 *	Also set the default values for user, instance and realm.
 *
 * Returns: TRUE if we didn't set the focus here,
 * 	FALSE if we did.
 */
static BOOL
kwin_initdialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
  LOGFONT lf;
  HDC hdc;
  char name[ANAME_SZ];

  position_dialog(hwnd);
  ticket_init_list(GetDlgItem(hwnd, IDD_TICKET_LIST));
  kwin_init_file_menu(hwnd);
  kwin_init_name(hwnd, (char *)lParam);
  hdc = GetDC(NULL);
  assert(hdc != NULL);

  memset(&lf, 0, sizeof(lf));
  lf.lfHeight = -MulDiv(9, GetDeviceCaps(hdc, LOGPIXELSY), 72);
  strcpy(lf.lfFaceName, "Arial");
  hfontdialog = CreateFontIndirect(&lf);
  assert(hfontdialog != NULL);

  if (hfontdialog == NULL) {
    ReleaseDC(NULL, hdc);

    return TRUE;
  }

  lf.lfHeight = -MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72);
  hfonticon = CreateFontIndirect(&lf);
  assert(hfonticon != NULL);

  if (hfonticon == NULL) {
    ReleaseDC(NULL, hdc);

    return TRUE;
  }

  ReleaseDC(NULL, hdc);

  set_dialog_font(hwnd, hfontdialog);
  GetDlgItemText(hwnd, IDD_LOGIN_NAME, name, sizeof(name));
  trim(name);

  if (strlen(name) > 0)
    SetFocus(GetDlgItem(hwnd, IDD_LOGIN_PASSWORD));
  else
    SetFocus(GetDlgItem(hwnd, IDD_LOGIN_NAME));

  ShowWindow(hwnd, dlgncmdshow);

  kwin_timer_id = SetTimer(hwnd, 1, KWIN_UPDATE_PERIOD, NULL);
  assert(kwin_timer_id != 0);

  return FALSE;
}


/*
 * Function: Process WM_DESTROY messages.  Delete the font
 *	created for use by the controls.
 */
static void
kwin_destroy(HWND hwnd)
{
  RECT r;

  ticket_destroy(GetDlgItem(hwnd, IDD_TICKET_LIST));

  if (hfontdialog != NULL)
    DeleteObject(hfontdialog);

  if (hfonticon != NULL)
    DeleteObject(hfonticon);

  kwin_save_file_menu(hwnd);
  GetWindowRect(hwnd, &r);
  cns_res.x = r.left;
  cns_res.y = r.top;
  cns_res.cx = r.right - r.left;
  cns_res.cy = r.bottom - r.top;

  KillTimer(hwnd, kwin_timer_id);
}


/*
 * Function: Retrievs item WindowRect in hwnd client
 *	coordiate system.
 *
 * Parameters:
 *	hwnditem - the item to retrieve
 *
 *	item - dialog in which into which to translate
 *
 *	r - rectangle returned
 */
static void
windowrect(HWND hwnditem, HWND hwnd, RECT *r)
{
  GetWindowRect(hwnditem, r);
  ScreenToClient(hwnd, (LPPOINT)&(r->left));
  ScreenToClient(hwnd, (LPPOINT)&(r->right));
}


/*
 * Function: Process WM_SIZE messages.  Resize the
 *	list and position the buttons attractively.
 */
static void
kwin_size(HWND hwnd, UINT state, int cxdlg, int cydlg)
{
#define listgap 8
  RECT r;
  RECT rdlg;
  int hmargin, vmargin;
  HWND hwnditem;
  int cx, cy;
  int i;
  int titlebottom;
  int editbottom;
  int listbottom;
  int gap;
  int left;
  int titleleft[IDD_MAX_TITLE - IDD_MIN_TITLE + 1];

  if (state == SIZE_MINIMIZED)
    return;

  GetClientRect(hwnd, &rdlg);

  /*
   * The ticket list title
   */
  hwnditem = GetDlgItem(hwnd, IDD_TICKET_LIST_TITLE);

  if (hwnditem == NULL)
    return;

  windowrect(hwnditem, hwnd, &r);
  hmargin = r.left;
  vmargin = r.top;
  cx = cxdlg - 2 * hmargin;
  cy = r.bottom - r.top;
  MoveWindow(hwnditem, r.left, r.top, cx, cy, TRUE);

  /*
   * The buttons
   */
  cx = 0;

  for (i = IDD_MIN_BUTTON; i <= IDD_MAX_BUTTON; i++) {
    hwnditem = GetDlgItem(hwnd, i);
    windowrect(hwnditem, hwnd, &r);
    if (i == IDD_MIN_BUTTON)
      hmargin = r.left;

    cx += r.right - r.left;
  }

  gap = (cxdlg - 2 * hmargin - cx) / (IDD_MAX_BUTTON - IDD_MIN_BUTTON);
  left = hmargin;
  for (i = IDD_MIN_BUTTON; i <= IDD_MAX_BUTTON; i++) {
    hwnditem = GetDlgItem(hwnd, i);
    windowrect(hwnditem, hwnd, &r);
    editbottom = -r.top;
    cx = r.right - r.left;
    cy = r.bottom - r.top;
    r.top = rdlg.bottom - vmargin - cy;
    MoveWindow(hwnditem, left, r.top, cx, cy, TRUE);

    left += cx + gap;
  }

  /*
   * Edit fields: stretch boxes, keeping the gap between boxes equal to
   * what it was on entry.
   */
  editbottom += r.top;

  hwnditem = GetDlgItem(hwnd, IDD_MIN_EDIT);
  windowrect(hwnditem, hwnd, &r);
  gap = r.right;
  hmargin = r.left;
  editbottom += r.bottom;
  titlebottom = -r.top;

  hwnditem = GetDlgItem(hwnd, IDD_MIN_EDIT + 1);
  windowrect(hwnditem, hwnd, &r);
  gap = r.left - gap;

  cx = cxdlg - 2 * hmargin - (IDD_MAX_EDIT - IDD_MIN_EDIT) * gap;
  cx = cx / (IDD_MAX_EDIT - IDD_MIN_EDIT + 1);
  left = hmargin;

  for (i = IDD_MIN_EDIT; i <= IDD_MAX_EDIT; i++) {
    hwnditem = GetDlgItem(hwnd, i);
    windowrect(hwnditem, hwnd, &r);
    cy = r.bottom - r.top;
    r.top = editbottom - cy;
    MoveWindow(hwnditem, left, r.top, cx, cy, TRUE);
    titleleft[i-IDD_MIN_EDIT] = left;

    left += cx + gap;
  }

  /*
   * Edit field titles
   */
  titlebottom += r.top;
  windowrect(GetDlgItem(hwnd, IDD_MIN_TITLE), hwnd, &r);
  titlebottom += r.bottom;
  listbottom = -r.top;

  for (i = IDD_MIN_TITLE; i <= IDD_MAX_TITLE; i++) {
    hwnditem = GetDlgItem(hwnd, i);
    windowrect(hwnditem, hwnd, &r);
    cx = r.right - r.left;
    cy = r.bottom - r.top;
    r.top = titlebottom - cy;
    MoveWindow(hwnditem, titleleft[i-IDD_MIN_TITLE], r.top, cx, cy, TRUE);
  }

  /*
   * The list
   */
  listbottom = r.top - listgap;
  hwnditem = GetDlgItem(hwnd, IDD_TICKET_LIST);
  windowrect(hwnditem, hwnd, &r);
  hmargin = r.left;
  cx = cxdlg - 2 * hmargin;
  cy = listbottom - r.top;
  MoveWindow(hwnditem, r.left, r.top, cx, cy, TRUE);
}


/*
 * Function: Process WM_GETMINMAXINFO messages
 */
static void
kwin_getminmaxinfo(HWND hwnd, LPMINMAXINFO lpmmi)
{
  lpmmi->ptMinTrackSize.x =
    (KWIN_MIN_WIDTH * LOWORD(GetDialogBaseUnits())) / 4;

  lpmmi->ptMinTrackSize.y =
    (KWIN_MIN_HEIGHT * HIWORD(GetDialogBaseUnits())) / 8;
}


/*
 * Function: Process WM_TIMER messages
 */
static void
kwin_timer(HWND hwnd, UINT timer_id)
{
  HWND hwndfocus;
  time_t t;
  time_t expiration;
  BOOL expired;
#ifdef KRB4
  CREDENTIALS c;
  int ncred;
  int i;
  char service[ANAME_SZ];
  char instance[INST_SZ];
  char realm[REALM_SZ];
#endif
#ifdef KRB5
  krb5_error_code code;
  krb5_cc_cursor cursor;
  krb5_creds cred;
  int n;
  char *s;
#endif

  if (timer_id != 1) {
    FORWARD_WM_TIMER(hwnd, timer_id, DefDlgProc);
    return;
  }

  expired = FALSE;
  ticket_init_list(GetDlgItem(hwnd, IDD_TICKET_LIST));

  if (alerted) {
    if (IsIconic(hwnd))
      InvalidateRect(hwnd, NULL, TRUE);

    return;
  }

#ifdef KRB4
  ncred = krb_get_num_cred();
  for (i = 1; i <= ncred; i++) {
    krb_get_nth_cred(service, instance, realm, i);

    if (_stricmp(service, "krbtgt") == 0) {
      /* Warn if ticket will expire w/i TIME_BUFFER seconds */
      krb_get_cred(service, instance, realm, &c);
      expiration = c.issue_date + (long)c.lifetime * 5L * 60L;
      t = TIME_BUFFER + time(NULL);

      if (t >= expiration) {
	expired = TRUE;
	/* Don't alert because of stale tickets */
	if (t >= expiration + KWIN_UPDATE_PERIOD / 1000) {
	  alerted = TRUE;

	  if (IsIconic(hwnd))
	    InvalidateRect(hwnd, NULL, TRUE);
	  return;
	}
	break;
      }
    }
  }
#endif

#ifdef KRB5
  code = krb5_cc_start_seq_get(k5_context, k5_ccache, &cursor);

  while (code == 0) {
    code = krb5_cc_next_cred(k5_context, k5_ccache, &cursor, &cred);
    if (code)
      break;
    n = krb5_princ_component(k5_context, cred.server, 0)->length;
    s = krb5_princ_component(k5_context, cred.server, 0)->data;
    if (n != KRB5_TGS_NAME_SIZE)
      continue;
    if (memcmp(KRB5_TGS_NAME, s, KRB5_TGS_NAME_SIZE))
      continue;

    /* Warn if ticket will expire w/i TIME_BUFFER seconds */
    expiration = cred.times.endtime;
    t = TIME_BUFFER + time(NULL);

    if (t >= expiration) {
      expired = TRUE;
      /* Don't alert because of stale tickets */
      if (t >= expiration + KWIN_UPDATE_PERIOD / 1000) {
	alerted = TRUE;

	if (IsIconic(hwnd))
	  InvalidateRect(hwnd, NULL, TRUE);
	return;
      }
      break;
    }
  }
  if (code == 0 || code == KRB5_CC_END)
    krb5_cc_end_seq_get(k5_context, k5_ccache, &cursor);
    
#endif

  if (!expired) {
    if (IsIconic(hwnd))
      InvalidateRect(hwnd, NULL, TRUE);

    return;
  }

  alerted = TRUE;

  if (beep)
    MessageBeep(MB_ICONEXCLAMATION);

  if (alert) {
    if (IsIconic(hwnd)) {
      hwndfocus = GetFocus();
      ShowWindow(hwnd, SW_RESTORE);
      SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
		   SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
      SetFocus(hwndfocus);
    }

    SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
		 SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);

    return;
  }

  if (IsIconic(hwnd))
    InvalidateRect(hwnd, NULL, TRUE);
}

/*
 * Function: Process WM_COMMAND messages
 */
static void
kwin_command(HWND hwnd, int cid, HWND hwndCtl, UINT codeNotify)
{
  char                      name[ANAME_SZ];
  char                      realm[REALM_SZ];
  char                      password[MAX_KPW_LEN];
  HCURSOR                   hcursor;
  BOOL                      blogin;
  HMENU                     hmenu;
  char                      menuitem[MAX_K_NAME_SZ + 3];
  char                      copyright[128];
  int                       id;
#ifdef KRB4
  char                      instance[INST_SZ];
  int                       lifetime;
  int                       krc;
#endif
#ifdef KRB5
  long                      lifetime;
  krb5_error_code           code;
  krb5_principal            principal;
  krb5_creds                creds;
  krb5_get_init_creds_opt   opts;
  gic_data                  gd;
#endif

#ifdef KRB4
  EnableWindow(GetDlgItem(hwnd, IDD_TICKET_DELETE), krb_get_num_cred() > 0);
#endif

#ifdef KRB5
  EnableWindow(GetDlgItem(hwnd, IDD_TICKET_DELETE), k5_get_num_cred(1) > 0);
#endif

  GetDlgItemText(hwnd, IDD_LOGIN_NAME, name, sizeof(name));
  trim(name);
  blogin = strlen(name) > 0;

  if (blogin) {
    GetDlgItemText(hwnd, IDD_LOGIN_REALM, realm, sizeof(realm));
    trim(realm);
    blogin = strlen(realm) > 0;
  }

  if (blogin) {
    GetDlgItemText(hwnd, IDD_LOGIN_PASSWORD, password, sizeof(password));
    blogin = strlen(password) > 0;
  }

  EnableWindow(GetDlgItem(hwnd, IDD_LOGIN), blogin);
  id = (blogin) ? IDD_LOGIN : IDD_PASSWORD_CR2;
  SendMessage(hwnd, DM_SETDEFID, id, 0);

  if (codeNotify != BN_CLICKED && codeNotify != 0 && codeNotify != 1)
    return; /* FALSE */

  /*
   * Check to see if this item is in a list of the ``recent hosts'' sort
   * of list, under the FILE menu.
   */
  if (cid >= IDM_FIRST_LOGIN && cid < IDM_FIRST_LOGIN + FILE_MENU_MAX_LOGINS) {
    hmenu = GetMenu(hwnd);
    assert(hmenu != NULL);

    hmenu = GetSubMenu(hmenu, 0);
    assert(hmenu != NULL);

    if (!GetMenuString(hmenu, cid, menuitem, sizeof(menuitem), MF_BYCOMMAND))
      return; /* TRUE */

    if (menuitem[0])
      kwin_init_name(hwnd, &menuitem[3]);

    return; /* TRUE */
  }

  switch (cid) {
  case IDM_EXIT:
    if (isblocking)
      WSACancelBlockingCall();
    WinHelp(hwnd, KERBEROS_HLP, HELP_QUIT, 0);
    PostQuitMessage(0);

    return; /* TRUE */

  case IDD_PASSWORD_CR2:                      /* Make CR == TAB */
    id = GetDlgCtrlID(GetFocus());
    assert(id != 0);

    if (id == IDD_MAX_EDIT)
      PostMessage(hwnd, WM_NEXTDLGCTL,
		  (WPARAM)GetDlgItem(hwnd, IDD_MIN_EDIT), MAKELONG(1, 0));
    else
      PostMessage(hwnd, WM_NEXTDLGCTL, 0, 0);

    return; /* TRUE */

  case IDD_LOGIN:
    if (isblocking)
      return; /* TRUE */

    GetDlgItemText(hwnd, IDD_LOGIN_NAME, name, sizeof(name));
    trim(name);
    GetDlgItemText(hwnd, IDD_LOGIN_REALM, realm, sizeof(realm));
    trim(realm);
    GetDlgItemText(hwnd, IDD_LOGIN_PASSWORD, password, sizeof(password));
    SetDlgItemText(hwnd, IDD_LOGIN_PASSWORD, "");  /* nuke the password */
    trim(password);

#ifdef KRB4
    GetDlgItemText(hwnd, IDD_LOGIN_INSTANCE, instance, sizeof(instance));
    trim(instance);
#endif

    hcursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
    lifetime = cns_res.lifetime;
    start_blocking_hook(BLOCK_MAX_SEC);

#ifdef KRB4
    lifetime = (lifetime + 4) / 5;
    krc = krb_get_pw_in_tkt(name, instance, realm, "krbtgt", realm,
			    lifetime, password);
#endif

#ifdef KRB5
    principal = NULL;
    
    /*
     * convert the name + realm into a krb5 principal string and parse it into a principal
     */
    sprintf(menuitem, "%s@%s", name, realm);
    code = krb5_parse_name(k5_context, menuitem, &principal);
    if (code)
      goto errorpoint;
    
    /*
     * set the various ticket options.  First, initialize the structure, then set the ticket
     * to be forwardable if desired, and set the lifetime.
     */
    krb5_get_init_creds_opt_init(&opts);
    krb5_get_init_creds_opt_set_forwardable(&opts, forwardable);
    krb5_get_init_creds_opt_set_tkt_life(&opts, lifetime * 60);
    
    /*
     * get the initial creds using the password and the options we set above
     */
    gd.hinstance = hinstance;
    gd.hwnd = hwnd;
    gd.id = ID_VARDLG;
    code = krb5_get_init_creds_password(k5_context, &creds, principal, password, 
					gic_prompter, &gd, 0, NULL, &opts);
    if (code)
      goto errorpoint;
    
    /*
     * initialize the credential cache
     */
    code = krb5_cc_initialize(k5_context, k5_ccache, principal);
    if (code)
      goto errorpoint;
    
    /*
     * insert the principal into the cache
     */
    code = krb5_cc_store_cred(k5_context, k5_ccache, &creds);
    
  errorpoint:
    
    if (principal)
      krb5_free_principal(k5_context, principal);

    end_blocking_hook();
    SetCursor(hcursor);
    kwin_set_default_focus(hwnd);
    
    if (code) {
      if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY)
	MessageBox(hwnd, "Password incorrect", NULL, 
		   MB_OK | MB_ICONEXCLAMATION);
      else 
	com_err(NULL, code, "while logging in");
    }
#endif /* KRB5 */
    
#ifdef KRB4
    if (krc != KSUCCESS) {
      MessageBox(hwnd, krb_get_err_text(krc),	"",
		 MB_OK | MB_ICONEXCLAMATION);

      return; /* TRUE */
    }
#endif

    kwin_save_name(hwnd);
    alerted = FALSE;

    switch (action) {
    case LOGIN_AND_EXIT:
      SendMessage(hwnd, WM_COMMAND, GET_WM_COMMAND_MPS(IDM_EXIT, 0, 0));
      break;

    case LOGIN_AND_MINIMIZE:
      ShowWindow(hwnd, SW_MINIMIZE);
      break;
    }

    return; /* TRUE */

  case IDD_TICKET_DELETE:
    if (isblocking)
      return; /* TRUE */

#ifdef KRB4
    krc = dest_tkt();
    if (krc != KSUCCESS)
      MessageBox(hwnd, krb_get_err_text(krc),	"",
		 MB_OK | MB_ICONEXCLAMATION);
#endif

#ifdef KRB5
    code = k5_dest_tkt();
#endif

    kwin_set_default_focus(hwnd);
    alerted = FALSE;

    return; /* TRUE */

  case IDD_CHANGE_PASSWORD:
    if (isblocking)
      return; /* TRUE */
    password_dialog(hwnd);
    kwin_set_default_focus(hwnd);

    return; /* TRUE */

  case IDM_OPTIONS:
    if (isblocking)
      return; /* TRUE */
    opts_dialog(hwnd);

    return; /* TRUE */

  case IDM_HELP_INDEX:
    WinHelp(hwnd, KERBEROS_HLP, HELP_INDEX, 0);

    return; /* TRUE */

  case IDM_ABOUT:
    ticket_init_list(GetDlgItem(hwnd, IDD_TICKET_LIST));
    if (isblocking)
      return; /* TRUE */

#ifdef KRB4
    strcpy(copyright, "        Kerberos 4 for Windows ");
#endif
#ifdef KRB5
    strcpy(copyright, "        Kerberos V5 for Windows ");
#endif
#ifdef _WIN32
    strncat(copyright, "32-bit\n", sizeof(copyright) - 1 - strlen(copyright));
#else
    strncat(copyright, "16-bit\n", sizeof(copyright) - 1 - strlen(copyright));
#endif
    strncat(copyright, "\n                Version 1.12\n\n",
            sizeof(copyright) - 1 - strlen(copyright));
#ifdef ORGANIZATION
    strncat(copyright, "          For information, contact:\n",
	    sizeof(copyright) - 1 - strlen(copyright));
    strncat(copyright, ORGANIZATION, sizeof(copyright) - 1 - strlen(copyright));
#endif
    MessageBox(hwnd, copyright, KWIN_DIALOG_NAME, MB_OK);

    return; /* TRUE */
  }

  return; /* FALSE */
}


/*
 * Function: Process WM_SYSCOMMAND messages by setting
 *	the focus to the password or name on restore.
 */
static void
kwin_syscommand(HWND hwnd, UINT cmd, int x, int y)
{
  if (cmd == SC_RESTORE)
    kwin_set_default_focus(hwnd);

  if (cmd == SC_CLOSE) {
    SendMessage(hwnd, WM_COMMAND, GET_WM_COMMAND_MPS(IDM_EXIT, 0, 0));
    return;
  }

  FORWARD_WM_SYSCOMMAND(hwnd, cmd, x, y, DefDlgProc);
}


/*
 * Function: Process WM_PAINT messages by displaying an
 *	informative icon when we are iconic.
 */
static void
kwin_paint(HWND hwnd)
{
  HDC hdc;
  PAINTSTRUCT ps;
  HICON hicon;
  time_t expiration = 0;
  time_t dt;
  char buf[20];
  RECT r;
#ifdef KRB4
  int i;
  int ncred;
  char service[ANAME_SZ];
  char instance[INST_SZ];
  char realm[REALM_SZ];
  CREDENTIALS c;
#endif
#ifdef KRB5
  krb5_error_code code;
  krb5_cc_cursor cursor;
  krb5_creds c;
  int n;
  char *service;
#endif

  if (!IsIconic(hwnd)) {
    FORWARD_WM_PAINT(hwnd, DefDlgProc);
    return;
  }

#ifdef KRB4
  ncred = krb_get_num_cred();

  for (i = 1; i <= ncred; i++) {
    krb_get_nth_cred(service, instance, realm, i);
    krb_get_cred(service, instance, realm, &c);
    if (_stricmp(c.service, "krbtgt") == 0) {
      expiration = c.issue_date - kwin_get_epoch()
	+ (long)c.lifetime * 5L * 60L;
      break;
    }
  }
#endif

#ifdef KRB5
  code = krb5_cc_start_seq_get(k5_context, k5_ccache, &cursor);

  while (code == 0) {
    code = krb5_cc_next_cred(k5_context, k5_ccache, &cursor, &c);
    if (code)
      break;
    n = krb5_princ_component(k5_context, c.server, 0)->length;
    service = krb5_princ_component(k5_context, c.server, 0)->data;
    if (n != KRB5_TGS_NAME_SIZE)
      continue;
    if (memcmp(KRB5_TGS_NAME, service, KRB5_TGS_NAME_SIZE))
      continue;
    expiration = c.times.endtime;
    break;
                
  }
  if (code == 0 || code == KRB5_CC_END)
    krb5_cc_end_seq_get(k5_context, k5_ccache, &cursor);
#endif

  hdc = BeginPaint(hwnd, &ps);
  GetClientRect(hwnd, &r);
  DefWindowProc(hwnd, WM_ICONERASEBKGND, (WPARAM)hdc, 0);

  if (expiration == 0) {
    strcpy(buf, KWIN_DIALOG_NAME);
    hicon = LoadIcon(hinstance, MAKEINTRESOURCE(IDI_KWIN));
  }
  else {
    hicon = kwin_get_icon(expiration);
    dt = (expiration - time(NULL)) / 60;

    if (dt <= 0)
      sprintf(buf, "%s - %s", KWIN_DIALOG_NAME, "Expired");
    else if (dt < 60) {
      dt %= 60;
      sprintf(buf, "%s - %ld min", KWIN_DIALOG_NAME, dt);
    }
    else {
      dt /= 60;
      sprintf(buf, "%s - %ld hr", KWIN_DIALOG_NAME, dt);
    }

    buf[sizeof(buf) - 1] = '\0';
    if (dt > 1)
      strncat(buf, "s", sizeof(buf) - 1 - strlen(buf));
  }

  DrawIcon(hdc, r.left, r.top, hicon);
  EndPaint(hwnd, &ps);
  SetWindowText(hwnd, buf);
}


/*
 * Function: Window procedure for the Kerberos control panel dialog.
 */
LRESULT CALLBACK
kwin_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  int n;

#if 0
  if (message == wm_kerberos_changed) {       /* Message from the ccache */
    n = ticket_init_list(GetDlgItem(hwnd, IDD_TICKET_LIST));
    EnableWindow(GetDlgItem(hwnd, IDD_TICKET_DELETE), n > 0);

    return 0;
  }
#endif

  switch (message) {
    HANDLE_MSG(hwnd, WM_GETMINMAXINFO, kwin_getminmaxinfo);

    HANDLE_MSG(hwnd, WM_DESTROY, kwin_destroy);

    HANDLE_MSG(hwnd, WM_MEASUREITEM, ticket_measureitem);

    HANDLE_MSG(hwnd, WM_DRAWITEM, ticket_drawitem);

  case WM_SETCURSOR:
    if (isblocking) {
      SetCursor(LoadCursor(NULL, IDC_WAIT));
      return TRUE;
    }
    break;

    HANDLE_MSG(hwnd, WM_SIZE, kwin_size);

    HANDLE_MSG(hwnd, WM_SYSCOMMAND, kwin_syscommand);

    HANDLE_MSG(hwnd, WM_TIMER, kwin_timer);

    HANDLE_MSG(hwnd, WM_PAINT, kwin_paint);
    
  case WM_ERASEBKGND:
    if (!IsIconic(hwnd))
      break;
    return 0;

  case WM_KWIN_SETNAME:
    kwin_init_name(hwnd, (char *)lParam);
  }

  return DefDlgProc(hwnd, message, wParam, lParam);
}


/*
 * Function: Dialog procedure called by the dialog manager
 *	to process dialog specific messages.
 */
static BOOL CALLBACK
kwin_dlg_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message) {
    HANDLE_MSG(hwnd, WM_INITDIALOG, kwin_initdialog);

    HANDLE_MSG(hwnd, WM_COMMAND, kwin_command);
  }

  return FALSE;
}


/*
 * Function: Initialize the kwin dialog class.
 *
 * Parameters:
 *	hinstance - the instance to initialize
 *
 * Returns: TRUE if dialog class registration is sucessfully, false otherwise.
 */
static BOOL
kwin_init(HINSTANCE hinstance)
{
  WNDCLASS class;
  ATOM rc;

  class.style = CS_HREDRAW | CS_VREDRAW;
  class.lpfnWndProc = (WNDPROC)kwin_wnd_proc;
  class.cbClsExtra = 0;
  class.cbWndExtra = DLGWINDOWEXTRA;
  class.hInstance = hinstance;
  class.hIcon = NULL;
  /*		LoadIcon(hinstance, MAKEINTRESOURCE(IDI_KWIN)); */
  class.hCursor = NULL;
  class.hbrBackground = NULL;
  class.lpszMenuName = NULL;
  class.lpszClassName = KWIN_DIALOG_CLASS;

  rc = RegisterClass(&class);
  assert(rc);

  return rc;
}


/*
 * Function: Initialize the KWIN application.  This routine should
 *	only be called if no previous instance of the application
 *	exists.  Currently it only registers a class for the kwin
 *	dialog type.
 *
 * Parameters:
 *	hinstance - the instance to initialize
 *
 * Returns: TRUE if initialized sucessfully, false otherwise.
 */
static BOOL
init_application(HINSTANCE hinstance)
{
  BOOL rc;

#if 0
#ifdef KRB4
  wm_kerberos_changed = krb_get_notification_message();
#endif

#ifdef KRB5
  wm_kerberos_changed = krb5_get_notification_message();
#endif
#endif

  rc = kwin_init(hinstance);

  return rc;
}


/*
 * Function: Quits the KWIN application.  This routine should
 *	be called when the last application instance exits.
 *
 * Parameters:
 *	hinstance - the instance which is quitting.
 *
 * Returns: TRUE if initialized sucessfully, false otherwise.
 */
static BOOL
quit_application(HINSTANCE hinstance)
{
  return TRUE;
}


/*
 * Function: Initialize the current instance of the KWIN application.
 *
 * Parameters:
 *	hinstance - the instance to initialize
 *
 *	ncmdshow - show flag to indicate wheather to come up minimized
 *		or not.
 *
 * Returns: TRUE if initialized sucessfully, false otherwise.
 */
static BOOL
init_instance(HINSTANCE hinstance, int ncmdshow)
{
  WORD versionrequested;
  WSADATA wsadata;
  int rc;
  int i;

  versionrequested = 0x0101;			/* We need version 1.1 */
  rc = WSAStartup(versionrequested, &wsadata);
  if (rc != 0) {
    MessageBox(NULL, "Couldn't initialize Winsock library", "",
	       MB_OK | MB_ICONSTOP);

    return FALSE;
  }

  if (versionrequested != wsadata.wVersion) {
    WSACleanup();
    MessageBox(NULL, "Winsock version 1.1 not available", "",
	       MB_OK | MB_ICONSTOP);

    return FALSE;
  }

#ifdef KRB5
  {
    krb5_error_code code;

    code = krb5_init_context(&k5_context);
    if (!code) {
#if 0				/* Not needed under windows */
      krb5_init_ets(k5_context);
#endif
      code = k5_init_ccache(&k5_ccache);
    }
    if (code) {
      com_err(NULL, code, "while initializing program");
      return FALSE;
    }
    k5_name_from_ccache(k5_ccache);
  }
#endif

  cns_load_registry();

  /*
   * Set up expiration action
   */
  alert = cns_res.alert;
  beep = cns_res.beep;

  /*
   * ticket options
   */
  forwardable = cns_res.forwardable;

  /*
   * Load clock icons
   */
  for (i = IDI_FIRST_CLOCK; i <= IDI_LAST_CLOCK; i++)
    kwin_icons[i - IDI_FIRST_CLOCK] = LoadIcon(hinstance, MAKEINTRESOURCE(i));

#ifdef KRB4
  krb_start_session(NULL);
#endif

  return TRUE;
}


/*
 * Function: Quits the current instance of the KWIN application.
 *
 * Parameters:
 *	hinstance - the instance to quit.
 *
 * Returns: TRUE if termination was sucessfully, false otherwise.
 */
static BOOL
quit_instance(HINSTANCE hinstance)
{
  int i;

#ifdef KRB4
  krb_end_session(NULL);
#endif

#ifdef KRB5     /* FIXME */
  krb5_cc_close(k5_context, k5_ccache);
#endif

  WSACleanup();

  /*
   * Unload clock icons
   */
  for (i = IDI_FIRST_CLOCK; i <= IDI_LAST_CLOCK; i++)
    DestroyIcon(kwin_icons[i - IDI_FIRST_CLOCK]);

  return TRUE;
}


/*
 * Function: Main routine called on program invocation.
 *
 * Parameters:
 *	hinstance - the current instance
 *
 *	hprevinstance - previous instance if one exists or NULL.
 *
 *	cmdline - the command line string passed by Windows.
 *
 *	ncmdshow - show flag to indicate wheather to come up minimized
 *		or not.
 *
 * Returns: TRUE if initialized sucessfully, false otherwise.
 */
int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hprevinstance, LPSTR cmdline, int ncmdshow)
{
  DLGPROC dlgproc;
  HWND hwnd;
  HACCEL haccel;
  MSG msg;
  char *p;
  char buf[MAX_K_NAME_SZ + 9];
  char name[MAX_K_NAME_SZ];

  strcpy(buf, cmdline);
  action = LOGIN_AND_RUN;
  name[0] = 0;
  p = strtok(buf, " ,");

  while (p != NULL) {
    if (_stricmp(p, "/exit") == 0)
      action = LOGIN_AND_EXIT;
    else if (_stricmp(p, "/minimize") == 0)
      action = LOGIN_AND_MINIMIZE;
    else
      strcpy(name, p);

    p = strtok(NULL, " ,");
  }

  dlgncmdshow = ncmdshow;
  hinstance = hinst;

#ifndef _WIN32
  /*
   * If a previous instance of this application exits, bring it
   * to the front and exit.
   *
   * This code is not compiled for WIN32, since hprevinstance will always
   * be NULL.
   */
  if (hprevinstance != NULL) {
    hwnd = FindWindow(KWIN_DIALOG_CLASS, NULL);

    if (IsWindow(hwnd) && IsWindowVisible(hwnd)) {
      if (GetWindowWord(hwnd, GWW_HINSTANCE) == hprevinstance) {
	if (name[0])
	  SendMessage(hwnd, WM_KWIN_SETNAME, 0, (LONG)name);

	ShowWindow(hwnd, ncmdshow);
	SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
		     SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);

	return FALSE;
      }
    }
  }

  if (hprevinstance == NULL)
#endif /* _WIN32 */

  if (!init_application(hinstance))
      return FALSE;

  if (!init_instance(hinstance, ncmdshow))
    return FALSE;

#ifdef _WIN32
  dlgproc = kwin_dlg_proc;
#else
  dlgproc = (FARPROC)MakeProcInstance(kwin_dlg_proc, hinstance);
  assert(dlgproc != NULL);

  if (dlgproc == NULL)
    return 1;
#endif

  hwnd = CreateDialogParam(hinstance, MAKEINTRESOURCE(ID_KWIN),
			   HWND_DESKTOP, dlgproc, (LONG)name);
  assert(hwnd != NULL);

  if (hwnd == NULL)
    return 1;
  haccel = LoadAccelerators(hinstance, MAKEINTRESOURCE(IDA_KWIN));
  assert(hwnd != NULL);

  while (GetMessage(&msg, NULL, 0, 0)) {
    if (!TranslateAccelerator(hwnd, haccel, &msg) &&
	!IsDialogMessage(hwnd, &msg)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }

  DestroyWindow(hwnd);

#ifndef _WIN32
  FreeProcInstance((FARPROC)dlgproc);
#endif

  cns_save_registry();

  return 0;
}


#if 0

#define WM_ASYNC_COMPLETED (WM_USER + 1)
#define GETHOSTBYNAME_CLASS "krb_gethostbyname"
static HTASK htaskasync;	/* Asynchronos call in progress */
static BOOL iscompleted;	/* True when async call is completed */

/*
 * This routine is called to cancel a blocking hook call within
 * the Kerberos library.  The need for this routine arises due
 * to bugs which exist in existing WINSOCK implementations.  We
 * blocking gethostbyname with WSAASyncGetHostByName.  In order
 * to cancel such an operation, this routine must be called.
 * Applications may call this routine in addition to calls to
 * WSACancelBlockingCall to get any sucy Async calls canceled.
 * Return values are as they would be for WSACancelAsyncRequest.
 */
int
krb_cancel_blocking_call(void)
{
  if (htaskasync == NULL)
    return 0;
  iscompleted = TRUE;

  return WSACancelAsyncRequest(htask);
}


/*
 * Window proceedure for temporary Windows created in
 * krb_gethostbyname.  Fields completion messages.
 */
LRESULT CALLBACK
krb_gethostbyname_wnd_proc(HWND hwnd, UINT message,
			   WPARAM wParam, LPARAM lParam)
{
  if (message == WM_ASYNC_COMPLETED) {
    iscompleted = TRUE;
    return 0;
  }
  
  return DefWindowProc(hwnd, message, wParam, lParam);
}


/*
 * The WINSOCK routine gethostbyname has a bug in both FTP and NetManage
 * implementations which causes the blocking hook, if any, not to be
 * called.  This routine attempts to work around the problem by using
 * the async routines to emulate the functionality of the synchronous
 * routines
 */
struct hostent *PASCAL
krb_gethostbyname(
		  const char *name)
{
  HWND hwnd;
  char buf[MAXGETHOSTSTRUCT];
  BOOL FARPROC blockinghook;
  WNDCLASS wc;
  static BOOL isregistered;
  
  blockinghook = WSASetBlockingHook(NULL);
  WSASetBlockingHook(blockinghook);

  if (blockinghook == NULL)
    return gethostbyname(name);

  if (RegisterWndClass() == NULL)
    return gethostbyname(name);

  if (!isregistered) {
    wc.style = 0;
    wc.lpfnWndProc = gethostbyname_wnd_proc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hlibinstance;
    wc.hIcon = NULL;
    wc.hCursor = NULL;
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = GETHOSTBYNAME_CLASS;

    if (!RegisterClass(&wc))
      return gethostbyname(name);

    isregistered = TRUE;
  }

  hwnd = CreateWindow(GETHOSTBYNAME_CLASS, "", WS_OVERLAPPED,
		      -100, -100, 0, 0, HWND_DESKTOP, NULL, hlibinstance, NULL);
  if (hwnd == NULL)
    return gethostbyname(name);

  htaskasync =
    WSAAsyncGetHostByName(hwnd, WM_ASYNC_COMPLETED, name, buf, sizeof(buf));
  b = blockinghook(NULL);
}

#endif  /* if 0 */

#ifdef KRB5

/*
 * Function: destroys all tickets in a k5 ccache
 *
 * Returns: K5 error code (0 == success)
 */
krb5_error_code
k5_dest_tkt(void)
{
  krb5_error_code code;
  krb5_principal princ;

  if (code = krb5_cc_get_principal(k5_context, k5_ccache, &princ)) {
    com_err(NULL, code, "while retrieving principal name");
    return code;
  }

  code = krb5_cc_initialize(k5_context, k5_ccache, princ);
  if (code != 0) {
    com_err(NULL, code, "when re-initializing cache");
    krb5_free_principal(k5_context, princ);
    return code;
  }

  krb5_free_principal(k5_context, princ);

  return code;
}

/*
 * 
 * k5_get_num_cred
 * 
 * Returns: number of creds in the credential cache, -1 on error
 * 
 */
int
k5_get_num_cred(int verbose)
{
  krb5_error_code code;
  krb5_cc_cursor cursor;
  krb5_creds c;
  int ncreds = 0;

  /* Turn off OPENCLOSE and leave open while we use ccache */
  if (code = krb5_cc_set_flags(k5_context, k5_ccache, 0)) {
    if (code == KRB5_FCC_NOFILE)
      return 0;
    if (verbose)
      com_err(NULL, code,
	      "while setting cache flags (ticket cache %s)",
	      krb5_cc_get_name(k5_context, k5_ccache));
    return -1;
  }

  if (code = krb5_cc_start_seq_get(k5_context, k5_ccache, &cursor)) {
    if (verbose)
      com_err(NULL, code, "while starting to retrieve tickets.");
    return -1;
  }

  while (1) {                                 /* Loop and get creds */
    code = krb5_cc_next_cred(k5_context, k5_ccache, &cursor, &c);
    if (code)
      break;
    ++ncreds;
  }

  if (code != KRB5_CC_END) {                  /* Error while looping??? */
    if (verbose)
      com_err(NULL, code, "while retrieving a ticket.");
    return -1;
  }

  if (code = krb5_cc_end_seq_get(k5_context, k5_ccache, &cursor)) {
    if (verbose)
      com_err(NULL, code, "while closing ccache.");
  } else if (code = krb5_cc_set_flags(k5_context, k5_ccache,
				      KRB5_TC_OPENCLOSE)) {
    if (verbose)
      com_err(NULL, code, "while closing ccache.");
  }

  return ncreds;
}

static int
k5_get_num_cred2()
{
  krb5_error_code code;
  krb5_cc_cursor cursor;
  krb5_creds c;
  int ncreds = 0;

  code = krb5_cc_start_seq_get(k5_context, k5_ccache, &cursor);
  if (code == KRB5_FCC_NOFILE)
    return 0;

  while (1) {
    code = krb5_cc_next_cred(k5_context, k5_ccache, &cursor, &c);
    if (code)
      break;
    ++ncreds;
  }

  if (code == KRB5_CC_END)
    krb5_cc_end_seq_get(k5_context, k5_ccache, &cursor);

  return ncreds;
}


/*
 * Function: Parses fullname into name and realm
 *
 * Parameters:
 *  name - buffer filled with name of user
 *  realm - buffer filled with realm of user
 *  fullname - string in form name.instance@realm
 *
 * Returns: 0
 */
int
k5_kname_parse(char *name, char *realm, char *fullname)
{
  char *ptr;                                  /* For parsing */

  ptr = strchr(fullname, '@');               /* Name, realm separator */

  if (ptr != NULL)                            /* Get realm */
    strcpy(realm, ptr + 1);
  else
    *realm = '\0';

  if (ptr != NULL) {                          /* Get the name */
    strncpy(name, fullname, ptr - fullname);
    name[ptr - fullname] = '\0';
  } else
    strcpy(name, fullname);

  ptr = strchr(name, '.');                   /* K4 compatability */
  if (ptr != NULL)
    *ptr = '\0';

  return 0;
}


/*
 * Function: Initializes ccache and catches illegal caches such as
 *  bad format or no permissions.
 *
 * Parameters:
 *  ccache - credential cache structure to use
 *
 * Returns: krb5_error_code
 */
krb5_error_code
k5_init_ccache(krb5_ccache *ccache)
{
  krb5_error_code code;
  krb5_principal princ;
  FILE *fp;

  code = krb5_cc_default(k5_context, ccache); /* Initialize the ccache */
  if (code)
    return code;

  code = krb5_cc_get_principal(k5_context, *ccache, &princ);
  if (code == KRB5_FCC_NOFILE) {              /* Doesn't exist yet */
    fp = fopen(krb5_cc_get_name(k5_context, *ccache), "w");
    if (fp == NULL)                         /* Can't open it */
      return KRB5_FCC_PERM;
    fclose (fp);
  }

  if (code) {                                 /* Bad, delete and try again */
    remove(krb5_cc_get_name(k5_context, *ccache));
    code = krb5_cc_get_principal(k5_context, *ccache, &princ);
    if (code == KRB5_FCC_NOFILE)            /* Doesn't exist yet */
      return 0;
    if (code)
      return code;
  }

  /*  krb5_free_principal(k5_context, princ); */

  return 0;
}


/*
 * 
 * Function: Reads the name and realm out of the ccache.
 * 
 * Parameters:
 *  ccache - credentials cache to get info from
 * 
 *  name - buffer to hold user name
 * 
 *  realm - buffer to hold the realm
 * 
 * 
 * Returns: TRUE if read names, FALSE if not
 * 
 */
int
k5_name_from_ccache(krb5_ccache k5_ccache)
{
  krb5_error_code code;
  krb5_principal princ;
  char name[ANAME_SZ];
  char realm[REALM_SZ];
  char *defname;

  if (code = krb5_cc_get_principal(k5_context, k5_ccache, &princ))
    return FALSE;

  code = krb5_unparse_name(k5_context, princ, &defname);
  if (code) {
    return FALSE;
  }

  k5_kname_parse(name, realm, defname);       /* Extract the components */
  strcpy(cns_res.name, name);
  strcpy(cns_res.realm, realm);

  return TRUE;
}
#endif /* KRB5 */
