//
// Text Server Version 1.0, a Windows Sockets Server
//
// Copyright 1993, Lee Murach
//
// Permission to use, modify, and distribute this software and its
// documentation for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and this permission notice appear in
// supporting documentation.  Lee makes no claims as to the suitability
// of this software for any purpose.
//
// Module TXTSRV, the only module of the Text Server, (TS) is both the
// user interface and the network interface.  Since TS is a server, its
// 'users' are TS clients, so the user interface requirements are
// rather simple. TS simply updates its display with outbound buffer
// traffic.  We also display winsock errors.
//
// TS is not a concurrent server; it processes one client request at a
// time.  This simplifies the design considerably.  TS uses
// WSAAsyncSelect() to receive the messages that notify of pending
// client requests, and prompt TS to deliver a reply.  However, TS
// switches to synchronous operation when retrieving a request, or
// delivering a reply.  TS reenables request notification (FD_ACCEPT)
// once it has finished its reply.  Meanwhile, other connection
// requests remain in the listen() queue.
//
// TS speaks the finger protocol, and will reply to finger (e.g., the
// winsock finger 3.x client).  We take the lazy way out, and assume
// the text file has <CR><LF> terminators. (as DOS files do)
//
// 3/26/93  Lee Murach     wrote this thing.
//

#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "winsock.h"
#include "txtsrv.h"

#define WSVERSION 0x101             // windows sockets version
#define SERVERPORT 79               // server listens on this port
#define DEFAULTFILE "default.txt"   // send this file for null queries
#define REQLEN 255                  // max length of request string
#define XFERBUFLEN 1024             // max # of character for send/recv
#define APIENTRY  PASCAL
#define WNDPROC   FARPROC

typedef struct                      // associates messages (or menu ids) 
{                                   // with a handler function
   UINT Code;
   LONG (*Fxn)(HWND, UINT, UINT, LONG);
} DECODEWORD;

typedef struct                      // associates an error code with text
{
   UINT err;
   char *sztext;
} ERRENTRY;

#define dim(x) (sizeof(x) / sizeof(x[0]))

HWND hFrame;                        // handle of main window
HANDLE hInst;                       // our instance
char szAppName[] = "Text Server";   // the server named text
SOCKET ListenSocket;                // awaits connection requests
SOCKET ConnectSocket;               // is connected to client
char Request[REQLEN + 1];           // holds request from client
int ReqLen;                         // length of request string
char Buf[XFERBUFLEN];               // transfer buffer holds in/outbound text
int BufLen = 0;                     // # of chars in xfer buffer

BOOL InitApp(HANDLE hInstance);
BOOL InitInstance(HANDLE hInstance, int nCmdShow);
BOOL FAR APIENTRY AboutDlgProc(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
LONG FAR APIENTRY FrameWndProc(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
LONG DoMenuAbout(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
LONG DoPaint(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
LONG DoCommand(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
LONG DoListen(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
LONG DoClose(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
LONG DoDestroy(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
LONG DoConnection(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
LONG DoGetRequest(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
LONG DoReply(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam);
void SendError(char *errstr);
void SendFile(FILE *infile);
void Blocking(SOCKET s);
void DebugOut(char FAR *lps, ...);
void DisplayWSError(void);
void CloseSockets(void);
void UpdateDisplay(int len);
void SizeWindow();
char *WSErrorString(UINT err);

#define WM_LISTEN       (WM_USER + 1)  // listen for connections
#define WM_CONNECTION   (WM_USER + 2)  // connection request awaits
#define WM_GETREQUEST   (WM_USER + 3)  // request is waiting
#define WM_REPLY        (WM_USER + 4)  // start reply

DECODEWORD frameMsgs[] =               // windows messages & handlers
{
   WM_LISTEN,     DoListen,
   WM_CONNECTION, DoConnection,
   WM_GETREQUEST, DoGetRequest,
   WM_REPLY,      DoReply,
   WM_COMMAND,    DoCommand,
   WM_PAINT,      DoPaint,
   WM_CLOSE,      DoClose,
   WM_DESTROY,    DoDestroy
};

DECODEWORD menuItems[] =            // menu items & associated handlers
{
   IDM_ABOUT,     DoMenuAbout,
   0,             0
};

ERRENTRY WSErrors[] =               // error text for windows sockets errors
{
   WSAVERNOTSUPPORTED,  "This version of Windows Sockets is not supported",
   WSASYSNOTREADY,      "Windows Sockets is not present or is not responding",
};

//
// WinMain -- Windows calls this to start the application.
//
int APIENTRY WinMain(HANDLE hInstance, HANDLE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow)
{
   WSADATA WSAData;               		      // windows sockets info return
   MSG msg;                                  // holds current message
   int err;

   hInst = hInstance;                        // save the instance handle
   if (hPrevInstance)                        // only one server, thank you
      return(FALSE);

   if (!(InitApp(hInstance) && InitInstance(hInstance, nCmdShow)))
      return(FALSE);                         // can't start, so bail

   if (err = WSAStartup(WSVERSION, &WSAData))// connect to winsock
   {
      MessageBox(hFrame, WSErrorString(err), szAppName, MB_ICONSTOP | MB_OK);
      DestroyWindow(hFrame);                 // kill application window &
   }                                         // signal app exit
   else
      PostMessage(hFrame, WM_LISTEN, 0, 0);  // get ready for clients
   
   while (GetMessage(&msg, NULL, 0, 0))      // loop til WM_QUIT
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }

   WSACleanup();                             // disconnect from winsock
   return(msg.wParam);                       // return to windows
}

//
// InitApp -- initialization for all instances of application.
// This registers the main window class.
//
BOOL InitApp(HANDLE hInstance)
{
   WNDCLASS    wndclass;

   wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
   wndclass.lpfnWndProc   = FrameWndProc;
   wndclass.cbClsExtra    = 0;
   wndclass.cbWndExtra    = 0;
   wndclass.hInstance     = hInstance;
   wndclass.hIcon         = LoadIcon(hInst, "Icon");
   wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wndclass.hbrBackground = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
   wndclass.lpszMenuName  = "Menu";
   wndclass.lpszClassName = szAppName;

   return(RegisterClass(&wndclass));
}

//
// InitInstance -- initializes this instance of app, creates windows.
//
BOOL InitInstance(HANDLE hInstance, int nCmdShow)
{
   hFrame = CreateWindow(  szAppName, szAppName,
                           WS_OVERLAPPEDWINDOW,
                           CW_USEDEFAULT, CW_USEDEFAULT,
                           CW_USEDEFAULT, CW_USEDEFAULT,
                           NULL, NULL, hInstance, NULL);
   if (!hFrame)
      return(FALSE);

   SizeWindow();                             // set window & char sizes
   ShowWindow(hFrame, nCmdShow);
   UpdateWindow(hFrame);

   return(TRUE);                             // connections
}

//
// SizeWindow -- sets window's character and external dimensions.
//
void SizeWindow()
{
   HDC hdc;
   TEXTMETRIC tm;
   RECT rect;
   int ychar, xchar;

   hdc = GetDC(hFrame);
   SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT));
   GetTextMetrics(hdc, &tm);
   ychar = tm.tmHeight + tm.tmExternalLeading;
   xchar = tm.tmAveCharWidth;
   ReleaseDC(hFrame, hdc);

   // set initial window width & height in chars
   GetWindowRect(hFrame, &rect);
   MoveWindow( hFrame, rect.left, rect.top,
               80 * xchar,
               24 * ychar + GetSystemMetrics(SM_CYCAPTION) +
               GetSystemMetrics(SM_CYMENU), FALSE);
}
 
//
// FrameWndProc -- callback function for application frame (main) window.
// Decodes message and routes to appropriate message handler. If no handler
// found, calls DefWindowProc.
// 
LONG FAR APIENTRY FrameWndProc(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   int i;

   for (i = 0; i < dim(frameMsgs); i++)
   {
      if (wMsg == frameMsgs[i].Code)
         return(*frameMsgs[i].Fxn)(hWnd, wMsg, wParam, lParam);
   }

   return(DefWindowProc(hWnd, wMsg, wParam, lParam));
}

//
// DoCommand -- demultiplexes WM_COMMAND messages resulting from menu
// selections, and routes to corresponding menu item handler.  Sends back
// any unrecognized messages to windows.
//
LONG DoCommand(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   int i;

   for (i = 0; i < dim(menuItems); i++)
   {
      if (wParam == menuItems[i].Code)
         return(*menuItems[i].Fxn)(hWnd, wMsg, wParam, lParam);
   }

   return(DefWindowProc(hWnd, wMsg, wParam, lParam));
}

//
// DoClose -- responds to close message by refusing pending client connects
// and terminating connections in progress, then kills window.
//
LONG DoClose(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   CloseSockets();

   DestroyWindow(hWnd);
   return(FALSE);
}

//
// DoDestroy -- posts a WM_QUIT message to the task's win queue, which
// causes the main translate & dispatch loop to exit, and the app to
// terminate.
//
LONG DoDestroy(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   PostQuitMessage(0);
   return(FALSE);
}

//
// DoPaint -- Paint the client window with the contents of the
// transfer buffer.
//
LONG DoPaint(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   RECT rect;                 // client window dimensions
   HDC hdc;
   PAINTSTRUCT ps;

   hdc = BeginPaint(hWnd, &ps);
   GetClientRect(hWnd, &rect);
   DrawText(hdc, Buf, BufLen, &rect, DT_EXPANDTABS);
   EndPaint(hWnd, &ps);
 
   return(FALSE);
}

//
// DoMenuAbout -- respond to "About..." menu selection by invoking the
// "About" dialog box.
//
LONG DoMenuAbout(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   WNDPROC lpProcAbout;

   lpProcAbout = MakeProcInstance((WNDPROC)AboutDlgProc, hInst);
   DialogBox(hInst, "AboutBox", hWnd, lpProcAbout);
   FreeProcInstance(lpProcAbout);

   return(FALSE);
}

//
// AboutDlgProc -- callback for the "About" dialog box
//
BOOL FAR APIENTRY AboutDlgProc(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   if ((wMsg == WM_COMMAND) && (wParam == IDOK))   // dismiss dialog if OK
      EndDialog(hWnd, 0);
 
   return(FALSE);                                  // otherwise just sit there
}

//
// UpdateDisplay -- display is xfer buffer, so we store the size, and
// force a repaint.
//
void UpdateDisplay(int len)
{
   BufLen = len;
   InvalidateRect(hFrame, NULL, TRUE);
}

//
// DoListen -- set up a listen socket to listen for client connection
// requests.  The WM_CONNECTION will signal that such is pending.
//
LONG DoListen(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   SOCKADDR_IN sin;

   ListenSocket = ConnectSocket = INVALID_SOCKET;

   if ((ListenSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
   {
      DisplayWSError();
      return(FALSE);
   }

   sin.sin_family = AF_INET;
   sin.sin_addr.s_addr = INADDR_ANY;
   sin.sin_port = htons(SERVERPORT);

   if (bind(ListenSocket, (LPSOCKADDR) &sin, sizeof(sin)))
   {
      DisplayWSError();
      return(FALSE);
   }

   if (listen(ListenSocket, 5))
   {
      DisplayWSError();
      return(FALSE);
   }

   WSAAsyncSelect(ListenSocket, hFrame, WM_CONNECTION, FD_ACCEPT);
   return(FALSE);
}

//
// DoConnection -- opens a connection to requesting client, and
// signals interest in data for read.  We'll receive a WM_GETREQUEST
// message when data has arrived.
//
LONG DoConnection(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   struct sockaddr sad;
   int len = sizeof(sad);

   /* take connection request off queue, and disable further
      notifications.  we can process only one request at a time. */

   ConnectSocket = accept(ListenSocket, &sad, &len);
   WSAAsyncSelect(ListenSocket, hFrame, 0, 0);

   if (ConnectSocket == INVALID_SOCKET)
   { 
      DisplayWSError();
      return(FALSE);
   }

   // notify us when a request (or part of one) has arrived
   WSAAsyncSelect(ConnectSocket, hFrame, WM_GETREQUEST, FD_READ);

   return(FALSE);
}

//
// CloseSockets -- shuts down listen, and any transfer in progress.
//
void CloseSockets(void)
{
   if (ListenSocket != INVALID_SOCKET);
      closesocket(ListenSocket);
   if (ConnectSocket != INVALID_SOCKET);
      closesocket(ConnectSocket);
}

//
// DoGetRequest -- synchronously receives the client's query, and places
// it in the request buffer.  We expect <CR><LF> terminator, but will accept
// end-of-stream (zero recv return).  We then ask for a write-space-available
// notification.
//
LONG DoGetRequest(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   char *p;
   int nchars;

   Request[0] = ReqLen = 0;
   Blocking(ConnectSocket);               // going synchronous now

   do // until we've received the whole request
   {
      nchars = recv(ConnectSocket, (LPSTR) Buf, sizeof(Buf), 0);

      if (nchars == SOCKET_ERROR)
      {
         DisplayWSError();
         return(FALSE);
      }

      Buf[nchars] = 0;                    // null terminate the buffer

      if ((ReqLen += nchars) > REQLEN)    // prevent request overflow
      {
         SendError("query too long");     // tell client
         return(FALSE);                   // request overflow, bail
      }

      strcat(Request, Buf);               // append to request
      p = strstr(Request, "\r\n");        // end-of-request?

   }
   while (nchars && !p);                  // stop for either end-of-stream
                                          // or <CR><LF> terminator
   if (p) *p = 0;                         // chop the terminator
   
   if (!Request[0])                       // translate null request to default
      strcpy(Request, DEFAULTFILE);

   // going asychronous now, this is will notify us of when to reply
   WSAAsyncSelect(ConnectSocket, hFrame, WM_REPLY, FD_WRITE);

   return(FALSE);
}

//
// DoReply -- synchronously replies to client with either the requested
// file or an error text, then reenables accept notifies, so we can
// process the next request.
//
LONG DoReply(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
   FILE *infile;

   Blocking(ConnectSocket);               // going synchronous now

   if (infile = fopen(Request, "rb"))     // open for read, binary
      SendFile(infile);
   else
      SendError(_strerror(NULL));         // file access error text

   fclose(infile);
   closesocket(ConnectSocket);
   ConnectSocket = INVALID_SOCKET;

   // we're ready for the next request now
   WSAAsyncSelect(ListenSocket, hFrame, WM_CONNECTION, FD_ACCEPT);

   return(FALSE);
}

//
// Blocking -- sets socket to block.
//
void Blocking(SOCKET s)
{
   u_long nonblock = FALSE;               // yes, we have no bananas

   WSAAsyncSelect(s, hFrame, 0, 0);       // turn off message notifications
   ioctlsocket(s, FIONBIO, &nonblock);    // set socket to blocking
}

//
// SendFile -- sends the infile down the ConnectSocket
// stream.
//
void SendFile(FILE *infile)
{
   int nchars;

   do
   {
      nchars = fread(Buf, 1, sizeof(Buf), infile);

      if (ferror(infile))
      {
         SendError(_strerror(NULL));
         UpdateDisplay(0);
         return;
      }

      if (nchars > 0)
      {
         UpdateDisplay(nchars);     // buffer changed, so repaint
   
         if (send(ConnectSocket, Buf, nchars, 0) == SOCKET_ERROR)
         {
            DisplayWSError();
            return;
         }
      }
   }
   while (!feof(infile));
}
   
//
// SendError -- sends an error string to client.
//
void SendError(char *errstr)
{
   static char *prefix = "\r\nserver error: ";

   send(ConnectSocket, prefix, strlen(prefix), 0);
   send(ConnectSocket, errstr, strlen(errstr), 0);

   closesocket(ConnectSocket);
   ConnectSocket = INVALID_SOCKET;
}

//
// DisplayWSError -- displays the winsock error in the client window.
//
void DisplayWSError(void)
{
   strcpy(Buf, WSErrorString(WSAGetLastError()));
   UpdateDisplay(strlen(Buf));

   closesocket(ConnectSocket);
   ConnectSocket = INVALID_SOCKET;
}

//
// WSErrorString -- translates winsock error to appropriate string.
//
char *WSErrorString(UINT err)
{
   int i;
   static char szerr[80];

   for (i = 0; i < dim(WSErrors); i++)
      if (err == WSErrors[i].err)
         return(WSErrors[i].sztext);

   sprintf(szerr, "Windows Sockets reports error %04x", err);
   return(szerr);
}

//
// DebugOut -- for outputting debug info to the AUX device (or debugger).
//
void DebugOut(char FAR *lps, ...)
{
   static char buf[80];
   static char FAR *args;

   args = (char FAR *) &lps + sizeof(lps);
   wvsprintf(buf, lps, args);
   OutputDebugString(buf);
}
