// $Id: Rlogin.cc,v 1.11 2002/10/26 04:15:44 vern Exp $
//
// Copyright (c) 1999, 2001, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#include "NetVar.h"
#include "Event.h"
#include "Rlogin.h"


RloginEndpoint::RloginEndpoint(RloginConn* arg_conn, int is_orig)
: TCP_EndpointLine(arg_conn, is_orig, 0, 0)
	{
	if ( is_orig )
		save_state = state = RLOGIN_FIRST_NULL;
	else
		save_state = state = RLOGIN_SERVER_ACK;

	num_bytes_to_scan = 0;
	}

RloginEndpoint::~RloginEndpoint()
	{
	}

void RloginEndpoint::DoDeliver(double t, int seq, int len, u_char* data)
	{
	for ( ; len > 0; --len, ++data )
		{
		if ( offset >= buf_len )
			Init(buf_len * 2);

		unsigned int c = data[0];

		switch ( state ) {
		case RLOGIN_FIRST_NULL:
			if ( TCP_Endpoint::state == TCP_PARTIAL ||
			     // We can be in closed if the data's due to
			     // a dataful FIN being the first thing we see.
			     TCP_Endpoint::state == TCP_CLOSED )
				{
				state = RLOGIN_UNKNOWN;
				++len, --data;	// put back c and reprocess
				continue;
				}

			if ( c == '\0' )
				state = RLOGIN_CLIENT_USER_NAME;
			else
				BadProlog();
			break;

		case RLOGIN_CLIENT_USER_NAME:
		case RLOGIN_SERVER_USER_NAME:
		case RLOGIN_TERMINAL_TYPE:
			buf[offset++] = c;
			if ( c == '\0' )
				{
				if ( state == RLOGIN_CLIENT_USER_NAME )
					{
					AsRloginConn()->ClientUserName(buf);
					state = RLOGIN_SERVER_USER_NAME;
					}

				else if ( state == RLOGIN_SERVER_USER_NAME )
					{
					AsRloginConn()->ServerUserName(buf);
					state = RLOGIN_TERMINAL_TYPE;
					}

				else if ( state == RLOGIN_TERMINAL_TYPE )
					{
					AsRloginConn()->TerminalType(buf);
					state = RLOGIN_LINE_MODE;
					AsRloginConn()->PrologFinished(this);
					}

				offset = 0;
				}
			break;

		case RLOGIN_SERVER_ACK:
			if ( TCP_Endpoint::state == TCP_PARTIAL ||
			     // We can be in closed if the data's due to
			     // a dataful FIN being the first thing we see.
			     TCP_Endpoint::state == TCP_CLOSED )
				{
				state = RLOGIN_UNKNOWN;
				++len, --data;	// put back c and reprocess
				continue;
				}

			if ( c == '\0' )
				{
				state = RLOGIN_LINE_MODE;
				AsRloginConn()->PrologFinished(this);
				}
			else
				state = RLOGIN_PRESUMED_REJECTED;
			break;

		case RLOGIN_IN_BAND_CONTROL_FF2:
			if ( c == 255 )
				state = RLOGIN_WINDOW_CHANGE_S1;
			else
				{
				// Put back the \ff that took us into
				// this state.
				buf[offset++] = 255;
				state = save_state;
				++len, --data;	// put back c and reprocess
				continue;
				}
			break;

		case RLOGIN_WINDOW_CHANGE_S1:
		case RLOGIN_WINDOW_CHANGE_S2:
			if ( c == 's' )
				{
				if ( state == RLOGIN_WINDOW_CHANGE_S1 )
					state = RLOGIN_WINDOW_CHANGE_S2;
				else
					{
					state = RLOGIN_WINDOW_CHANGE_REMAINDER;
					num_bytes_to_scan = 8;
					}
				}
			else
				{ // Unknown control, or we're confused.
				state = RLOGIN_UNKNOWN;

				// Put back what we've consumed.
				unsigned char buf[64];
				int n = 0;
				buf[n++] = '\xff';
				buf[n++] = '\xff';
				buf[n++] = 's';

				if ( state == RLOGIN_WINDOW_CHANGE_S2 )
					buf[n++] = 's';

				DoDeliver(t, seq - n, n, buf);
				}
			break;

		case RLOGIN_WINDOW_CHANGE_REMAINDER:
			if ( --num_bytes_to_scan == 0 )
				state = save_state;
			break;

		case RLOGIN_LINE_MODE:
		case RLOGIN_UNKNOWN:
		case RLOGIN_PRESUMED_REJECTED:
			if ( state == RLOGIN_LINE_MODE &&
			     ((RloginEndpoint*) peer)->RloginState() ==
				     RLOGIN_PRESUMED_REJECTED )
				{
				conn->Weird("rlogin_text_after_rejected");
				state = RLOGIN_UNKNOWN;
				}

			if ( c == '\n' || c == '\r' ) // CR or LF (RFC 1282)
				{
				if ( c == '\n' && last_char == '\r' )
					// Compress CRLF to just 1 termination.
					;
				else
					{
					buf[offset] = '\0';
					conn->NewLine(this, offset, buf, 0);
					offset = 0;
					break;
					}
				}

			else if ( c == 255 && conn->IsOrig(this) &&
				  state != RLOGIN_PRESUMED_REJECTED )
				{
				save_state = state;
				state = RLOGIN_IN_BAND_CONTROL_FF2;
				}

			else
				buf[offset++] = c;

			last_char = c;
			break;

		default:
			internal_error("bad state in RloginEndpoint::DoDeliver");
			break;
		}
		}
	}

void RloginEndpoint::BadProlog()
	{
	conn->Weird("bad_rlogin_prolog");
	state = RLOGIN_UNKNOWN;
	}


RloginConn::RloginConn(NetSessions* s, HashKey* k, double t, const ConnID* id,
		const struct tcphdr* tp)
: LoginConn(s, k, t, id, tp)
	{
	}

void RloginConn::BuildEndpoints()
	{
	orig = new RloginEndpoint(this, 1);
	resp = new RloginEndpoint(this, 0);
	}

void RloginConn::ClientUserName(const char* s)
	{
	if ( client_name )
		internal_error("multiple rlogin client names");

	client_name = new StringVal(s);
	}

void RloginConn::ServerUserName(const char* s)
	{
	++num_user_lines_seen;
	++login_prompt_line;
	AddUserText(s);
	}

void RloginConn::TerminalType(const char* s)
	{
	if ( login_terminal )
		{
		val_list* vl = new val_list;

		vl->append(BuildConnVal());
		vl->append(new StringVal(s));

		ConnectionEvent(login_terminal, vl);
		}
	}

void RloginConn::PrologFinished(RloginEndpoint* /* endp */)
	{
	}
