mirror of
https://github.com/zeek/zeek.git
synced 2025-10-09 10:08:20 +00:00
703 lines
13 KiB
C++
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);
|
|
}
|
|
|