* TELNETd.c	Example server using InternetCT request model.  A simple
 *				TELNET server is implemented.  Upon connection it will:
 *
 *				1. Display a welcome banner.
 *				2. Display the contents of a message file (if found).
 *				3. Echo anything sent.
 *				4. Close the connection if CTLD is recieved.
 *				5. Close the connection after the timeout period is reached
 *				   without any activity.
 *
 * 				Several (memory limited) sessions can be started with the 
 *				server at one time.  The entire server is one process and is 
 *				completely event driven.
 *

* define DEBUG *

* If you define DEBUG, by removing the comment delimiters above, a log will 
 * be produced during execution containing the output of the printf()s 
 * throughout the program.  After recompiling and linking, we suggest 
 * preventing TELNETd from becoming a system service, by specifying NO to 
 * parameter 2 (Convert to Sys?).
 *

* Change History
 *
 * DRH	920801	Initial.
 * DRH	920908	Changed RQ_OPENRTCLOCK to OPENRTCLOCK
 *				Removed param functions to Lib>param.c
 *

include stdio.h
include std.h
include inetrq.h
include timer.h
include TELNETd.h

*.< Local Functions >*

unsigned int			initialize();
unsigned int			main();
void					post_listen();
SCB						* create_cb();
void					delete_cb();
void					idle_timeout();
void					send_message_file();
void					process_buf();
void					process_cmd();

*.< Imported Functions >*

extern unsigned int		TcpGetSetup();			* ICT procedural interface *
extern unsigned int		OpenTCPConnection();
extern unsigned int		CloseTCPConnection();

												* Lib> *
extern unsigned int		os_start_timer();		* timer.c *
extern void				os_service_timer();
extern unsigned int		os_stop_timer();
extern void				tick();
extern void				start_timer();
extern void				stop_timer();

extern IRCB				* address_of_ircb();	* inetrq.c *
extern unsigned int		inet_rq_open();
extern unsigned int		inet_rq_close();
extern unsigned int		inet_rq_get_tcb();
extern unsigned int		inet_rq_send();
extern unsigned int		inet_rq_receive();

extern void				get_param_yes_no();		* param.c *
extern void				get_param_unsigned();

extern unsigned int		rgParam();				* CTOS procedural interface *
extern unsigned int		NlsYesOrNo();
extern unsigned int		CheckErc();
extern unsigned int		AllocExch();
extern unsigned int		SetMsgRet();
extern unsigned int		ConvertToSys();
extern unsigned int		SetPartitionName();
extern unsigned int		Wait();

*.< c Library >*

	* only non integer returning functions defined *
extern char				* calloc();

*.< Local Data >*

ifdef DEBUG
FILE					* debugfp;
endif

	* How many kilobytes to allocate, parameter 1 *
unsigned int			k				= 30;
	* Should we convert to a system service, parameter 2 *
unsigned char			convert_to_sys	= TRUE;

	* Exchange used by timer and inetrq interface *
unsigned int			server_exch;

	* Timer used to recover from failed attempts to post a listen *
TIMER					server_timer;

	* Debugging aid, set when post_listen goes wrong *
unsigned int			server_erc;

	* Work space to construct the welcome banner *
char					version[21];		* For example, 92080112:03:05 *
char					banner_fmt[] =	TELNET dummy login Server (s).\r\n;
char					banner[80];

	* TcpGetSetup parameters, all returned. *
unsigned long int		ip_addr;
unsigned int			mss;
unsigned int			window;
unsigned int			ttl;
char					hostname[HOSTNAMELEN + 1];
unsigned int			shostnameret;

char					partition_name[]		= TELNETd;

	* File name that's sent when a connection is made. *
char					message_filename[]		= [Sys]<INetCT>TELNETd.Msg;

	* Messages, etc. *
char					msg_echo[] = 
\r\nNow in ECHO mode. <CTL>d to close connection. 30 second idle timer.\r\n;

char					msg_bye[] =
				\r\nClosing connection. Goodbye \r\n;

char					msg_memory[] =	No Memory\r\n;

*.< Imported Data >*

extern unsigned char	sbverrun[];			* Provided by linker *
extern unsigned int		fdevelopement;

*======================.========< Initialize >==============================*
unsigned int initialize()
{
	unsigned int		len;
	char				* cp;
	unsigned int		handle;
	
ifdef DEBUG
fprintf( debugfp, Initialize:\n);
endif

		* Allocate exchange for timer use *
	CheckErc( AllocExch( server_exch));

		* Copy the version number for use in banner *
	memcpy( version, sbverrun[1], min( sbverrun[0], (sizeof(version)1)));

		* Get local host identity information *
	CheckErc( TCPGetSetup( ip_addr, mss, window, ttl,
					 	   hostname, HOSTNAMELEN, shostnameret));
	hostname[shostnameret] = '\0';

		* Check for TELNET server already installed by trying to connect. *
	if( OpenTCPConnection(	0, hostname, shostnameret, 
							TELNET_PORT, TCP_ACTIVE, 
							0,0,0, NULLCHAR, 0, handle) == ERC_OK) {
		CloseTCPConnection( handle);
		len = sprintf( banner, Telnet server already active\n);
		SetMsgRet( banner, len);
		Exit( 0);
	}

		* Setup banner *
	len = sprintf( banner, banner_fmt, version);

		* How much should we allocate? *
	get_param_unsigned( 1, 0, k);
	
		* Check for Convert to Sys parameter *
	get_param_yes_no( 2, 0, convert_to_sys);
	
		* Allocate and free some memory so that it's in use when and if we 
		 * convert to sys.  
		 *
	free( calloc( k, 1024));

	if( convert_to_sys) {
		SetMsgRet( banner, len);
		CheckErc( ConvertToSys());
		exit();
	}

	SetPartitionName( 0, partition_name, strlen( partition_name));

		* Start the Real Time Clock (RTC) OS timer.  The minimum granularity 
		 * such a timer is 110 of a second.  The third parameter, tick, is the
		 * name of a function called by the os timer at each tick of the 
		 * clock.
		 *
	os_start_timer( TIMER_GRANULARITY, server_exch, tick);

		* In the post_listen() function when something goes wrong, this timer 
		 * is started so that post_listen() will be retried again and again.
		 *
	server_timer.start = SERVER_TIMER_VALUE;
	server_timer.func = post_listen;
	server_timer.arg = NULLCHAR;
}*======================.======< TELNET dummy >=============================*
unsigned int main()
{
	REQUEST					* rq;
	IRCB					* ircb;
	unsigned int			rq_erc;
	SCB						* scb;


ifdef DEBUG
	unsigned int			len;
fdevelopement = TRUE;
if( ( debugfp = fopen( [Sys]<INetCT>TELNETd.Debug, w)) == NULL) {
		len = sprintf( banner, Could not open debug file\n);
		SetMsgRet( banner, len);
		Exit( 0);
}
else
	fdevelopement = FALSE;
endif

ifdef DEBUG
fprintf( debugfp, Main:\n);
endif

	initialize();

	post_listen( NULLCHAR);

	for(;;) {
			* Wait for the responses *
		checkerc( Wait( server_exch, rq));

			* We get one of these every second, so we'll check them first. *
		if( rq>rqcode == OPENRTCLOCK) {
			os_service_timer();
			continue;
		}

			* Pull the ircb out of the request.  If we can't get an ircb,
			 * assume the request is bogus and continue.
			 *
		if( ( ircb = address_of_ircb( rq)) == NULLIRCB) {

ifdef DEBUG
fprintf( debugfp, Main:\taddress_of_ircb() was BOGUS\n);
endif

			continue;
		}

		rq_erc = rq>ercret;
		scb = ircb>user_cb;		* NULLCHAR until we get past LISTEN *

ifdef DEBUG
fprintf( debugfp, Main: rq_erc=u, scb=lx\n, rq_erc, scb);
fflush( debugfp);
endif


			* Handle each of the request responses. *
		switch( rq>rqcode  REQUEST_BASE) {
		case ORC_OPEN_TCP_CONNECTION_NO_WAIT:

ifdef DEBUG
fprintf( debugfp, Main:\tOPEN\n);
endif

				* BASIC CONCEPT:	Servers should use 
				 *					OPEN_TCP_CONNECTION_NO_WAIT and do GET_TCB 
				 *					processing, at least until the connection 
				 *					reaches the ESTABLISHed state.  The reason 
				 *					is that OPEN_TCP_CONNECTION does not 
				 *					return until the connection reaches the 
				 *					ESTABLISHed state.  That can be 
				 *					unacceptable when the TCP open handshake 
				 *					is slow to complete or does not complete 
				 *					at all, because after receiving the 
				 *					initial SYN there is no new listen posted 
				 *					until (or if) we reach ESTABLISHed.  The 
				 *					GET_TCB processing can catch the change to 
				 *					SYN_RECEIVED and post the new listen at 
				 *					that point.
				 *

			if( rq_erc == ERC_OK) {
				inet_rq_get_tcb( ircb);
			} else {
					* delete_cb does a lot of cleanup processing and will 
					 * handle closing the connection, if necessary.
					 *
				delete_cb( ircb, scb);
				post_listen( NULLCHAR);
			}
			break;

		case ORC_CLOSE_TCP_CONNECTION:
		case ORC_ABORT_TCP_CONNECTION:

ifdef DEBUG
fprintf( debugfp, Main:\tCLOSE\n);
endif

			delete_cb( ircb, scb);
			break;

		case ORC_SEND_TCP_MESSAGE:

ifdef DEBUG
fprintf( debugfp, Main:\tSEND\n);
endif

				* BASIC CONCEPT:	The responses to SEND_TCP_MESSAGE requests 
				 *					come back as soon as InternetCT is able 
				 *					to buffer the data.  That does not mean 
				 *					the data has reached its destination.
				 *
			if( rq_erc == ERC_OK) {
				start_timer( scb>timeout);
					* This is how we put out the message file *
				if( scb>fsending_message) {
					send_message_file( ircb, scb);
				}
			} else {
				delete_cb( ircb, scb);
			}
			break;

		case ORC_RECEIVE_TCP_MESSAGE:

ifdef DEBUG
fprintf( debugfp, Main:\tRECEIVE\n);
endif

				* BASIC CONCEPT:	The responses to RECEIVE_TCP_MESSAGE 
				 *					requests come back as soon as InternetCT 
				 *					has data to deliver.  InternetCT will 
				 *					return as much data as it has buffered, up 
				 *					to the maximum you specify.  InternetCT 
				 *					does not wait until it can fill your whole 
				 *					request
				 *
			if( rq_erc == ERC_OK) {
					* process_buf() does all the work, including issuing 
					 * another RECEIVE_TCP_MESSAGE request.
					 *
				process_buf( ircb, scb);
			} else {
				delete_cb( ircb, scb);
			}
			break;

		case ORC_GET_TCB:

ifdef DEBUG
fprintf( debugfp, Main:\tGET_TCB\n);
endif

			if( rq_erc == ERC_OK) {
					* We're only doing get_tcb's during the opening connection
					 * phase.  We're not interested in receive events or 
					 * changes in the transmit window.  This bit is rather 
					 * standard for a server.  Checking the other notice types 
					 * is not normally done.
					 *
				if( ircb>tcb.notice_type == NOTICE_STATE) {
					switch( ircb>tcb.state) {
					case LISTEN:

ifdef DEBUG
fprintf( debugfp, Main:\t\t\tState Change  LISTEN\n);
endif

						* There will always be a state change to LISTEN for 
						 * passive opens.  Just reissue the get_tcb.
						 *
						inet_rq_get_tcb( ircb);
						break;
					case SYN_RECEIVED:

ifdef DEBUG
fprintf( debugfp, Main:\t\t\tState Change  SYN_RECEIVED\n);
endif

							* Depending upon the swiftness of the TCP 
							 * handshake between the client and InternetCT, 
							 * we may or may not see a state change to 
							 * SYN_RECEIVED.  If we do get one, allocate a 
							 * control block, start the timer just in case we 
							 * never get to ESTABLISHed, and post another 
							 * listen (all done in create_cb()).  Then issue 
							 * another inet_rq_get_tcb() to pick up the state 
							 * change to ESTABLISHed.
							 *
						create_cb( ircb);
						inet_rq_get_tcb( ircb);
						break;

					case ESTABLISHED:

ifdef DEBUG
fprintf( debugfp, Main:\t\t\tState Change  ESTABLISHED:\n);
endif

							* Allocate the SCB (if not allocated in 
							 * SYN_RECEIVED), post a receive, send the 
							 * banner, open the message file, and (re)start 
							 * the idle timer.  create_cb() does some setup 
							 * and will close the connection, if it fails.
							 * DO NOT issue additional inet_rq_get_tcb().
							 *
						if( (scb = create_cb( ircb)) = NULLSCB) {

ifdef DEBUG
fprintf( debugfp, Main:\t\t\tState Change  ESTABLISHED: valid SCB\n);
endif

							inet_rq_receive( ircb, scb>buf, SBUF_MAX);
							inet_rq_send( ircb, 0, 0, banner, strlen( banner));
							send_message_file( ircb, scb);
							start_timer( scb>timeout);
						}
						break;
					}			* End switch on connection state*
				} else {	* End notice type NOTICE_STATE  *
ifdef DEBUG
fprintf( debugfp, Main:\t\tOther Notice Type = u\n, ircb>tcb.notice_type);
endif
						* Other notice types DO NOT occur before we reach
						 * ESTABLISHED.  Famous last words Be defensive
						 *
					inet_rq_get_tcb( ircb);
				}

			} else {	* rq_erc = ERC_OK *
				delete_cb( ircb, scb);
			} 
			break;
		}	* End switch rq>code *
	}	* End for *
}
*======================.=======< Post Listen >==============================*
void post_listen( dummy)
unsigned char			* dummy;
{
	IRCB				* ircb;


ifdef DEBUG
fprintf( debugfp, Post Listen:\n);
endif

		* Called to start things going and whenever a connection is 
		 * established to listen again.  Also called when server_timer runs 
		 * down.  server_timer is only started in this function when something 
		 * goes wrong.
		 *
		 * BASIC CONCEPT:	An IRCB contains all the connection specific 
		 *					information for a server session.  Items specific 
		 *					to a particular server are maintained in some sort 
		 *					of server control block.
		 *


		* Doesn't hurt if not started *
	stop_timer( server_timer);

		* An IRCB is all that's needed for the listen.  The server control 
		 * block will be allocated and referenced from within the IRCB when a 
		 * connection is established.
		 *
	if( ( ircb = calloc( 1, sizeof( IRCB))) == NULLCHAR) {

ifdef DEBUG
fprintf( debugfp, Post Listen:\tcalloc IRCB failed\n);
endif

		server_erc = ERC_NO_MEMORY;
		start_timer( server_timer);
	} else {
		ircb>self_ref = ircb;
		if( ( server_erc = inet_rq_open( ircb, server_exch, 0, 
								  INETCT_OPEN_NO_WAIT, TELNET_PORT, 0, 
								  TCP_PASSIVE, NULLCHAR, 0)) = ERC_OK) {

ifdef DEBUG
fprintf( debugfp, Post Listen:\tinet_rq_open failed\n);
endif

			start_timer( server_timer);
			free( ircb);
		}
	}
}
*======================.========< Create CB >===============================*
SCB * create_cb( ircb)

IRCB					* ircb;
{
	SCB					* scb;


ifdef DEBUG
fprintf( debugfp, Create CB:\n);
endif

		* BASIC CONCEPT:	Server control blocks contain all the session 
		 *					specific information needed to carry on the 
		 *					business of a server, EXCEPT the connection 
		 *					information contained in an IRCB. 
		 *

		* Because this function can be called during SYN_RECEIVE andor 
		 * ESTABLISH processing we don't want to reallocate, if it is called 
		 * from both.
		 *
	if( ircb>user_cb == NULLCHAR) {
		if( ( scb = (SCB *) calloc( 1, sizeof(SCB))) == NULLSCB) {

ifdef DEBUG
fprintf( debugfp, Create CB:\tscb DID NOT EXIST\n);
endif

				* We have no SCB so delete_cb() won't do the close *
			inet_rq_close( ircb, INETCT_ABORT);
		} else {
				* Link the control blocks together for later reference *
			scb>ircb		= ircb;
			ircb>user_cb	= (char *)scb;

				* Setup the idle timer and start it ticking *
			scb>timeout.start	= IDLE_TIMER_VALUE;
			scb>timeout.func	= idle_timeout;
			scb>timeout.arg	= ircb;
			start_timer( scb>timeout);

		}
			* Prepare another listen *
		post_listen( NULLCHAR);
	} else {

ifdef DEBUG
fprintf( debugfp, Create CB:\tscb EXISTED, ircb>cb=lx\n, ircb>user_cb);
endif

		scb = (SCB *) ircb>user_cb;
	}

	return( scb);
}
*======================.========< Delete CB >===============================*
void delete_cb( ircb, scb)

IRCB					* ircb;
SCB						* scb;
{

ifdef DEBUG
fprintf( debugfp, Delete CB:\n);
endif

		* Close the connection, if we haven't already done so. *
	if( scb = NULLSCB  scb>fclosed) {

ifdef DEBUG
fprintf( debugfp, Delete CB:\tactive SCB  connection, Close Connection\n);
endif

		inet_rq_close( ircb, INETCT_CLOSE);
		scb>fclosed = TRUE;
	}
		* Always stop the timer, there's no longer any reason to have one. *
	if( scb = NULLSCB) {

ifdef DEBUG
fprintf( debugfp, Delete CB:\tactive SCB, Stop Idle Timer\n);
endif

		stop_timer( scb>timeout);
	}

		* If there are still outstanding request don't do anything else.
		 * Otherwise, free everything, we're done.
		 *
		 * BASIC CONCEPT:	Outstanding requests (most likely TCP_RECEIVE) 
		 *					will be ERC'ed when we close the connection.  The 
		 *					IRCB structure, and the request blocks it holds, 
		 *					are in allocated storage.  If we free the IRCB and 
		 *					then try to process the ERC'ed requests it's 
		 *					likely we'll produce Erc 80 protection exceptions.
		 *
	if( ircb>rqs_out == 0) {

ifdef DEBUG
fprintf( debugfp, Delete CB:\tircb>rqs_out = 0\n);
endif

			* Free the control block *
		if( scb = NULLSCB) {

ifdef DEBUG
fprintf( debugfp, Delete CB:\t\tscb = NULLSCB, free(scb)\n);
endif

			free( (char *) scb);
		}

ifdef DEBUG
fprintf( debugfp, Delete CB:\t\tfree(ircb)\n);
endif

		free( (char *) ircb);
	}
}
*======================.=======< Idle Timeout >=============================*
void idle_timeout( ircb)

IRCB					* ircb;
{

ifdef DEBUG
fprintf( debugfp, Idle Timeout:\n);
endif

	delete_cb( ircb, ircb>user_cb);
}
*======================.====< Send Message File >===========================*
void send_message_file( ircb, scb)

IRCB					* ircb;
SCB						* scb;
{

ifdef DEBUG
fprintf( debugfp, Send Message File:\n);
endif

		* Open and send the contents of a message file.  This function is 
		 * first called when we reach ESTABLISHED.  At that point, it will 
		 * open the file, if possible, set the fsending_message flag and 
		 * return.  Thereafter, it is called when the SEND_TCP_MESSAGE request 
		 * returns, as long as the flag is still set.
		 *

	if( scb>fsending_message) {

ifdef DEBUG
fprintf( debugfp, Send Message File:\tOpening file\n);
endif

		if( ( scb>fp = fopen( message_filename, r)) = NULL) {
			scb>fsending_message = TRUE;
		}
	} else {

ifdef DEBUG
fprintf( debugfp, Send Message File:\tRead file\n);
endif

		fgets( scb>buf, SBUF_MAX, scb>fp);
		if( feof( scb>fp) || ferror( scb>fp)) {

ifdef DEBUG
fprintf( debugfp, Send Message File:\t\tRead Error or EOF, close file\n);
endif

			scb>fsending_message = FALSE;
			fclose( scb>fp);
			inet_rq_send( ircb, 0, 0, msg_echo, strlen( msg_echo));
		} else {

ifdef DEBUG
fprintf( debugfp, Send Message File:\tSend from file\n);
endif

			inet_rq_send( ircb, 0, 0, scb>buf, strlen( scb>buf));
		}
	}
}
*======================.=====< Process Buffer >============================*
void process_buf( ircb, scb)

IRCB					* ircb;
SCB						* scb;
{
	unsigned int		i;
	unsigned int		sbuf;


ifdef DEBUG
fprintf( debugfp, Process Buffer:\n);
endif

		* Run through the received buffer assembling a command line.  This 
		 * must work across separately received buffers because we can't be 
		 * sure to get a whole line in one receive request from 
		 * InternetCT.  In this case the unit for processing is a line 
		 * terminated by a linefeed (\n) character.  In other cases, it might 
		 * be a particular number of bytes (record size), either hard coded or 
		 * selfdefined in the received data.  When we've received a line 
		 * or reached the maximun reasonable size (SCMD_MAX), we process it as 
		 * a command.  Using this technique, it is also possible to find and 
		 * process more than one line in a buffer.
		 *
		 * BASIC CONCEPT:	TCP transports streams of octets (bytes) not 
		 *					records.  While, what is sent is guaranteed to 
		 *					be received in the order it was sent, it is not 
		 *					guaranteed to be in the same size chunks.
		 *
		 * ALTERNATIVE:		It is also possible to implement a record 
		 *					protocol by using inet_rq_get_tcb to process 
		 *					ircb>tcb.notice_type NOTICE_RECEIVE and then only 
		 *					issue inet_rq_receive when ircb>tcb.count has 
		 *					reached the right amount.
		 *

	for( i = 0; i < ircb>smsgret; i++) {
		switch( scb>buf[i]) {
		case CTL_D:			* Goodbye. *

ifdef DEBUG
fprintf( debugfp, Process Buffer:\tCtlD\n);
endif

			inet_rq_send( ircb, 0, 0, msg_bye, strlen( msg_bye));
			delete_cb( ircb, scb);
			return;
			break;

		case '\n':			* End of Line *

ifdef DEBUG
fprintf( debugfp, Process Buffer:\tNewLine\n);
endif

			scb>cmd[scb>scmd++] = scb>buf[i];
			scb>cmd[scb>scmd] = '\0';
			process_cmd( ircb, scb, scb>cmd, scb>scmd);
			scb>scmd = 0;
			break;

		default:
			if( scb>scmd < SCMD_MAX) {
				scb>cmd[scb>scmd++] = scb>buf[i];
			} else {
				scb>cmd[SCMD_MAX] = '\0';
				process_cmd( ircb, scb, scb>cmd, SCMD_MAX);
				scb>scmd = 0;
			}
			break;
		}
	}
		* If the connection is closed due to processing in process_cmd(), 
		 * issuing inet_rq_receive() here will return an erc 61509 
		 * Connection Closed, but we don't care.
		 *	

ifdef DEBUG
fprintf( debugfp, Process Buffer:\tinet_rq_receive(), start_timer()\n);
endif

	inet_rq_receive( ircb, scb>buf, SBUF_MAX);
	start_timer( scb>timeout);
}
*======================.=====< Process Command >============================*
void process_cmd( ircb, scb, cmd, scmd)

IRCB					* ircb;
SCB						* scb;
char					* cmd;
unsigned int			scmd;
{

ifdef DEBUG
fprintf( debugfp, Process Command:\n);
endif

		* Most of the processing for a command processor would be done here 
		 * and in lower levels, but this is an example of using InternetCT 
		 * not how to implement any particular process.
		 *
	switch( *cmd) {
	case TELNET_OPT_ESC:	* Ignore 'em *

ifdef DEBUG
fprintf( debugfp, Process Command:TELNET_OPT_ESC\n);
endif

		break;
	default:				* Echo *

ifdef DEBUG
fprintf( debugfp, Process Command:\tEcho\n);
endif
		inet_rq_send( ircb, 0, 0, cmd, scmd);
		break;
	}
}
ircb>rqs_out = 0\n);
endif

			* Free the co