zeek/src/NVT.cc
2011-08-04 15:21:18 -05:00

703 lines
13 KiB
C++

// See the file "COPYING" in the main distribution directory for copyright.
#include "config.h"
#include <stdlib.h>
#include "NVT.h"
#include "NetVar.h"
#include "Event.h"
#include "TCP.h"
#define IS_3_BYTE_OPTION(c) (c >= 251 && c <= 254)
#define TELNET_OPT_SB 250
#define TELNET_OPT_SE 240
#define TELNET_OPT_IS 0
#define TELNET_OPT_SEND 1
#define TELNET_OPT_WILL 251
#define TELNET_OPT_WONT 252
#define TELNET_OPT_DO 253
#define TELNET_OPT_DONT 254
#define TELNET_IAC 255
TelnetOption::TelnetOption(NVT_Analyzer* arg_endp, unsigned int arg_code)
{
endp = arg_endp;
code = arg_code;
flags = 0;
active = 0;
}
void TelnetOption::RecvOption(unsigned int type)
{
TelnetOption* peer = endp->FindPeerOption(code);
if ( ! peer )
reporter->InternalError("option peer missing in TelnetOption::RecvOption");
// WILL/WONT/DO/DONT are messages we've *received* from our peer.
switch ( type ) {
case TELNET_OPT_WILL:
if ( SaidDont() || peer->SaidWont() || peer->IsActive() )
InconsistentOption(type);
peer->SetWill();
if ( SaidDo() )
peer->SetActive(1);
break;
case TELNET_OPT_WONT:
if ( peer->SaidWill() && ! SaidDont() )
InconsistentOption(type);
peer->SetWont();
if ( SaidDont() )
peer->SetActive(0);
break;
case TELNET_OPT_DO:
if ( SaidWont() || peer->SaidDont() || IsActive() )
InconsistentOption(type);
peer->SetDo();
if ( SaidWill() )
SetActive(1);
break;
case TELNET_OPT_DONT:
if ( peer->SaidDo() && ! SaidWont() )
InconsistentOption(type);
peer->SetDont();
if ( SaidWont() )
SetActive(0);
break;
default:
reporter->InternalError("bad option type in TelnetOption::RecvOption");
}
}
void TelnetOption::RecvSubOption(u_char* /* data */, int /* len */)
{
}
void TelnetOption::SetActive(int is_active)
{
active = is_active;
}
void TelnetOption::InconsistentOption(unsigned int /* type */)
{
endp->Event(inconsistent_option);
}
void TelnetOption::BadOption()
{
endp->Event(bad_option);
}
void TelnetTerminalOption::RecvSubOption(u_char* data, int len)
{
if ( len <= 0 )
{
BadOption();
return;
}
if ( data[0] == TELNET_OPT_SEND )
return;
if ( data[0] != TELNET_OPT_IS )
{
BadOption();
return;
}
endp->SetTerminal(data + 1, len - 1);
}
#define ENCRYPT_SET_ALGORITHM 0
#define ENCRYPT_SUPPORT_ALGORITM 1
#define ENCRYPT_REPLY 2
#define ENCRYPT_STARTING_TO_ENCRYPT 3
#define ENCRYPT_NO_LONGER_ENCRYPTING 4
#define ENCRYPT_REQUEST_START_TO_ENCRYPT 5
#define ENCRYPT_REQUEST_NO_LONGER_ENCRYPT 6
#define ENCRYPT_ENCRYPT_KEY 7
#define ENCRYPT_DECRYPT_KEY 8
void TelnetEncryptOption::RecvSubOption(u_char* data, int len)
{
if ( ! active )
{
InconsistentOption(0);
return;
}
if ( len <= 0 )
{
BadOption();
return;
}
unsigned int opt = data[0];
if ( opt == ENCRYPT_REQUEST_START_TO_ENCRYPT )
++did_encrypt_request;
else if ( opt == ENCRYPT_STARTING_TO_ENCRYPT )
{
TelnetEncryptOption* peer =
(TelnetEncryptOption*) endp->FindPeerOption(code);
if ( ! peer )
reporter->InternalError("option peer missing in TelnetEncryptOption::RecvSubOption");
if ( peer->DidRequest() || peer->DoingEncryption() ||
peer->Endpoint()->AuthenticationHasBeenAccepted() )
{
endp->SetEncrypting(1);
++doing_encryption;
}
else
InconsistentOption(0);
}
}
#define HERE_IS_AUTHENTICATION 0
#define SEND_ME_AUTHENTICATION 1
#define AUTHENTICATION_STATUS 2
#define AUTHENTICATION_NAME 3
#define AUTH_REJECT 1
#define AUTH_ACCEPT 2
void TelnetAuthenticateOption::RecvSubOption(u_char* data, int len)
{
if ( len <= 0 )
{
BadOption();
return;
}
switch ( data[0] ) {
case HERE_IS_AUTHENTICATION:
{
TelnetAuthenticateOption* peer =
(TelnetAuthenticateOption*) endp->FindPeerOption(code);
if ( ! peer )
reporter->InternalError("option peer missing in TelnetAuthenticateOption::RecvSubOption");
if ( ! peer->DidRequestAuthentication() )
InconsistentOption(0);
}
break;
case SEND_ME_AUTHENTICATION:
++authentication_requested;
break;
case AUTHENTICATION_STATUS:
if ( len <= 1 )
{
BadOption();
return;
}
if ( data[1] == AUTH_REJECT )
endp->AuthenticationRejected();
else if ( data[1] == AUTH_ACCEPT )
endp->AuthenticationAccepted();
else
{
// Don't complain, there may be replies we don't
// know about.
}
break;
case AUTHENTICATION_NAME:
{
char* auth_name = new char[len];
safe_strncpy(auth_name, (char*) data + 1, len);
endp->SetAuthName(auth_name);
}
break;
default:
BadOption();
}
}
#define ENVIRON_IS 0
#define ENVIRON_SEND 1
#define ENVIRON_INFO 2
#define ENVIRON_VAR 0
#define ENVIRON_VAL 1
#define ENVIRON_ESC 2
#define ENVIRON_USERVAR 3
void TelnetEnvironmentOption::RecvSubOption(u_char* data, int len)
{
if ( len <= 0 )
{
BadOption();
return;
}
if ( data[0] == ENVIRON_SEND )
//### We should track the dialog and make sure both sides agree.
return;
if ( data[0] != ENVIRON_IS && data[0] != ENVIRON_INFO )
{
BadOption();
return;
}
--len; // Discard code.
++data;
while ( len > 0 )
{
int code1, code2;
char* var_name = ExtractEnv(data, len, code1);
char* var_val = ExtractEnv(data, len, code2);
if ( ! var_name || ! var_val ||
(code1 != ENVIRON_VAR && code1 != ENVIRON_USERVAR) ||
code2 != ENVIRON_VAL )
{
// One of var_name/var_val might be set; avoid leak.
delete [] var_name;
delete [] var_val;
BadOption();
break;
}
static_cast<TCP_ApplicationAnalyzer*>
(endp->Parent())->SetEnv(endp->IsOrig(),
var_name, var_val);
}
}
char* TelnetEnvironmentOption::ExtractEnv(u_char*& data, int& len, int& code)
{
code = data[0];
if ( code != ENVIRON_VAR && code != ENVIRON_VAL &&
code != ENVIRON_USERVAR )
return 0;
// Move past code.
--len;
++data;
// Find the end of this piece of the option.
u_char* data_end = data + len;
u_char* d;
for ( d = data; d < data_end; ++d )
{
if ( *d == ENVIRON_VAR || *d == ENVIRON_VAL || *d == ENVIRON_USERVAR )
break;
if ( *d == ENVIRON_ESC )
{
++d; // move past ESC
if ( d >= data_end )
return 0;
break;
}
}
int size = d - data;
char* env = new char[size+1];
// Now copy into env.
int d_ind = 0;
int i;
for ( i = 0; i < size; ++i )
{
if ( data[d_ind] == ENVIRON_ESC )
++d_ind;
env[i] = data[d_ind];
++d_ind;
}
env[i] = '\0';
data = d;
len -= size;
return env;
}
void TelnetBinaryOption::SetActive(int is_active)
{
endp->SetBinaryMode(is_active);
active = is_active;
}
void TelnetBinaryOption::InconsistentOption(unsigned int /* type */)
{
// I don't know why, but this gets turned on redundantly -
// doesn't do any harm, so ignore it. Example is
// in ex/redund-binary-opt.trace.
}
NVT_Analyzer::NVT_Analyzer(Connection* conn, bool orig)
: ContentLine_Analyzer(AnalyzerTag::NVT, conn, orig)
{
peer = 0;
is_suboption = last_was_IAC = pending_IAC = 0;
IAC_pos = 0;
num_options = 0;
authentication_has_been_accepted = encrypting_mode = binary_mode = 0;
auth_name = 0;
}
NVT_Analyzer::~NVT_Analyzer()
{
for ( int i = 0; i < num_options; ++i )
delete options[i];
delete [] auth_name;
}
TelnetOption* NVT_Analyzer::FindOption(unsigned int code)
{
int i;
for ( i = 0; i < num_options; ++i )
if ( options[i]->Code() == code )
return options[i];
TelnetOption* opt = 0;
if ( i < NUM_TELNET_OPTIONS )
{ // Maybe we haven't created this option yet.
switch ( code ) {
case TELNET_OPTION_BINARY:
opt = new TelnetBinaryOption(this);
break;
case TELNET_OPTION_TERMINAL:
opt = new TelnetTerminalOption(this);
break;
case TELNET_OPTION_ENCRYPT:
opt = new TelnetEncryptOption(this);
break;
case TELNET_OPTION_AUTHENTICATE:
opt = new TelnetAuthenticateOption(this);
break;
case TELNET_OPTION_ENVIRON:
opt = new TelnetEnvironmentOption(this);
break;
}
}
if ( opt )
options[num_options++] = opt;
return opt;
}
TelnetOption* NVT_Analyzer::FindPeerOption(unsigned int code)
{
assert(peer);
return peer->FindOption(code);
}
void NVT_Analyzer::AuthenticationAccepted()
{
authentication_has_been_accepted = 1;
Event(authentication_accepted, PeerAuthName());
}
void NVT_Analyzer::AuthenticationRejected()
{
authentication_has_been_accepted = 0;
Event(authentication_rejected, PeerAuthName());
}
const char* NVT_Analyzer::PeerAuthName() const
{
assert(peer);
return peer->AuthName();
}
void NVT_Analyzer::SetTerminal(const u_char* terminal, int len)
{
if ( login_terminal )
{
val_list* vl = new val_list;
vl->append(BuildConnVal());
vl->append(new StringVal(new BroString(terminal, len, 0)));
ConnectionEvent(login_terminal, vl);
}
}
void NVT_Analyzer::SetEncrypting(int mode)
{
encrypting_mode = mode;
SetSkipDeliveries(mode);
if ( mode )
Event(activating_encryption);
}
#define MAX_DELIVER_UNIT 128
void NVT_Analyzer::DoDeliver(int len, const u_char* data)
{
// This code is very similar to that for TCP_ContentLine. We
// don't virtualize out the differences because some of them
// would require per-character function calls, too expensive.
if ( pending_IAC )
{
ScanOption(seq, len, data);
return;
}
// Add data up to IAC or end.
for ( ; len > 0; --len, ++data )
{
if ( offset >= buf_len )
InitBuffer(buf_len * 2);
int c = data[0];
if ( binary_mode && c != TELNET_IAC )
c &= 0x7f;
#define EMIT_LINE \
{ \
buf[offset] = '\0'; \
ForwardStream(offset, buf, IsOrig()); \
offset = 0; \
}
switch ( c ) {
case '\r':
if ( CRLFAsEOL() & CR_as_EOL )
EMIT_LINE
else
buf[offset++] = c;
break;
case '\n':
if ( last_char == '\r' )
{
if ( CRLFAsEOL() & CR_as_EOL )
// we already emited, skip
;
else
{
--offset; // remove '\r'
EMIT_LINE
}
}
else if ( CRLFAsEOL() & LF_as_EOL )
EMIT_LINE
else
{
if ( Conn()->FlagEvent(SINGULAR_LF) )
Conn()->Weird("line_terminated_with_single_LF");
buf[offset++] = c;
}
break;
case '\0':
if ( last_char == '\r' )
// Allow a NUL just after a \r - Solaris
// Telnet servers generate these, and they
// appear harmless.
;
else if ( flag_NULs )
CheckNUL();
else
buf[offset++] = c;
break;
case TELNET_IAC:
pending_IAC = 1;
IAC_pos = offset;
is_suboption = 0;
buf[offset++] = c;
ScanOption(seq, len - 1, data + 1);
return;
default:
buf[offset++] = c;
break;
}
if ( ! (CRLFAsEOL() & CR_as_EOL) &&
last_char == '\r' && c != '\n' && c != '\0' )
{
if ( Conn()->FlagEvent(SINGULAR_CR) )
Weird("line_terminated_with_single_CR");
}
last_char = c;
}
}
void NVT_Analyzer::ScanOption(int seq, int len, const u_char* data)
{
if ( len <= 0 )
return;
if ( IAC_pos == offset - 1 )
{ // All we've seen so far is the IAC.
unsigned int code = data[0];
if ( code == TELNET_IAC )
{
// An escaped 255, throw away the second
// instance and drop the IAC state.
pending_IAC = 0;
last_char = code;
}
else if ( code == TELNET_OPT_SB )
{
is_suboption = 1;
last_was_IAC = 0;
buf[offset++] = code;
}
else if ( IS_3_BYTE_OPTION(code) )
{
is_suboption = 0;
buf[offset++] = code;
}
else
{
// We've got the whole 2-byte option.
SawOption(code);
// Throw it and the IAC away.
--offset;
pending_IAC = 0;
}
// Recurse to munch on the remainder.
DeliverStream(len - 1, data + 1, IsOrig());
return;
}
if ( ! is_suboption )
{
// We now have the full 3-byte option.
SawOption(u_char(buf[offset-1]), data[0]);
// Delete the option.
offset -= 2; // code + IAC
pending_IAC = 0;
DeliverStream(len - 1, data + 1, IsOrig());
return;
}
// A suboption. Spin looking for end.
for ( ; len > 0; --len, ++data )
{
unsigned int code = data[0];
if ( last_was_IAC )
{
last_was_IAC = 0;
if ( code == TELNET_IAC )
{
// This is an escaped IAC, eat
// the second copy.
continue;
}
if ( code != TELNET_OPT_SE )
// BSD Telnet treats this case as terminating
// the suboption, so that's what we do here
// too. Below we make sure to munch on the
// new IAC.
BadOptionTermination(code);
int opt_start = IAC_pos + 2;
int opt_stop = offset - 1;
int opt_len = opt_stop - opt_start;
SawSubOption((const char*)&buf[opt_start], opt_len);
// Delete suboption.
offset = IAC_pos;
pending_IAC = is_suboption = 0;
if ( code == TELNET_OPT_SE )
DeliverStream(len - 1, data + 1, IsOrig());
else
{
// Munch on the new (broken) option.
pending_IAC = 1;
IAC_pos = offset;
buf[offset++] = TELNET_IAC;
DeliverStream(len, data, IsOrig());
}
return;
}
else
{
buf[offset++] = code;
last_was_IAC = (code == TELNET_IAC);
}
}
}
void NVT_Analyzer::SawOption(unsigned int /* code */)
{
}
void NVT_Analyzer::SawOption(unsigned int code, unsigned int subcode)
{
TelnetOption* opt = FindOption(subcode);
if ( opt )
opt->RecvOption(code);
}
void NVT_Analyzer::SawSubOption(const char* subopt, int len)
{
unsigned int subcode = u_char(subopt[0]);
++subopt;
--len;
TelnetOption* opt = FindOption(subcode);
if ( opt )
opt->RecvSubOption((u_char*) subopt, len);
}
void NVT_Analyzer::BadOptionTermination(unsigned int /* code */)
{
Event(bad_option_termination);
}