
type ftp_port: record;

%%{
#include "zeek/Reporter.h"

static zeek::RecordValPtr parse_port(const char* line)
	{
	auto r = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::ftp_port);

	int bytes[6];
	if ( line && sscanf(line, "%d,%d,%d,%d,%d,%d",
			&bytes[0], &bytes[1], &bytes[2],
			&bytes[3], &bytes[4], &bytes[5]) == 6 )
		{
		int good = 1;

		for ( int i = 0; i < 6; ++i )
			if ( bytes[i] < 0 || bytes[i] > 255 )
				{
				good = 0;
				break;
				}

		uint32_t addr = (bytes[0] << 24) | (bytes[1] << 16) |
				(bytes[2] << 8) | bytes[3];
		uint32_t port = (bytes[4] << 8) | bytes[5];

		// Since port is unsigned, no need to check for < 0.
		if ( port > 65535 )
			{
			port = 0;
			good = 0;
			}

		r->Assign(0, zeek::make_intrusive<zeek::AddrVal>(htonl(addr)));
		r->Assign(1, zeek::val_mgr->Port(port, TRANSPORT_TCP));
		r->Assign(2, good);
		}
	else
		{
		r->Assign(0, zeek::make_intrusive<zeek::AddrVal>(uint32_t(0)));
		r->Assign(1, zeek::val_mgr->Port(0, TRANSPORT_TCP));
		r->Assign(2, false);
		}

	return r;
	}

static zeek::RecordValPtr parse_eftp(const char* line)
	{
	auto r = zeek::make_intrusive<zeek::RecordVal>(zeek::BifType::Record::ftp_port);

	int net_proto = 0;	// currently not used
	zeek::IPAddr addr;	// unspecified IPv6 address (all 128 bits zero)
	int port = 0;
	int good = 0;

	if ( line )
		{
		while ( isspace(*line) )	// skip whitespace
			++line;

		char delimiter = *line;
		char* next_delim;

		if ( *line )
			{
			good = 1;
			++line;	// skip delimiter

			net_proto = strtol(line, &next_delim, 10);
			if ( *next_delim != delimiter )
				good = 0;

			line = next_delim;
			if ( *line )
				++line;

			if ( *line && *line != delimiter )
				{
				const char* nptr = strchr(line, delimiter);
				if ( nptr == NULL )
					{
					nptr = line + strlen(line);
					good = 0;
					}

				std::string s(line, nptr-line); // extract IP address
				struct in6_addr result;
				good = zeek::IPAddr::ConvertString(s.c_str(), &result) ? 1 : 0;
				if ( good )
					addr = zeek::IPAddr(result);
				}

			line = strchr(line, delimiter);

			if ( line != NULL )
				{
				++line;	// now the port
				port = strtol(line, &next_delim, 10);
				if ( *next_delim != delimiter )
					good = 0;

				if ( port < 0 || port > 65535 )
					{
					port = 0;
					good = 0;
					}
				}
			}

		}

	r->Assign(0, zeek::make_intrusive<zeek::AddrVal>(addr));
	r->Assign(1, zeek::val_mgr->Port(port, TRANSPORT_TCP));
	r->Assign(2, good);

	return r;
	}
%%}

## Converts a string representation of the FTP PORT command to an
## :zeek:type:`ftp_port`.
##
## s: The string of the FTP PORT command, e.g., ``"10,0,0,1,4,31"``.
##
## Returns: The FTP PORT, e.g., ``[h=10.0.0.1, p=1055/tcp, valid=T]``.
##
## .. zeek:see:: parse_eftp_port parse_ftp_pasv parse_ftp_epsv fmt_ftp_port
function parse_ftp_port%(s: string%): ftp_port
	%{
	return parse_port(s->CheckString());
	%}

## Converts a string representation of the FTP EPRT command (see :rfc:`2428`)
## to an :zeek:type:`ftp_port`.  The format is
## ``"EPRT<space><d><net-prt><d><net-addr><d><tcp-port><d>"``,
## where ``<d>`` is a delimiter in the ASCII range 33-126 (usually ``|``).
##
## s: The string of the FTP EPRT command, e.g., ``"|1|10.0.0.1|1055|"``.
##
## Returns: The FTP PORT, e.g., ``[h=10.0.0.1, p=1055/tcp, valid=T]``.
##
## .. zeek:see:: parse_ftp_port parse_ftp_pasv parse_ftp_epsv fmt_ftp_port
function parse_eftp_port%(s: string%): ftp_port
	%{
	return parse_eftp(s->CheckString());
	%}

## Converts the result of the FTP PASV command to an :zeek:type:`ftp_port`.
##
## str: The string containing the result of the FTP PASV command.
##
## Returns: The FTP PORT, e.g., ``[h=10.0.0.1, p=1055/tcp, valid=T]``.
##
## .. zeek:see:: parse_ftp_port parse_eftp_port parse_ftp_epsv fmt_ftp_port
function parse_ftp_pasv%(str: string%): ftp_port
	%{
	const char* s = str->CheckString();
	const char* line = strchr(s, '(');
	if ( line )
		++line;	// move past '('
	else if ( (line = strstr(s, "PORT")) )
		line += 5;	// Skip over
	else if ( (line = strchr(s, ',')) )
		{ // Look for comma-separated list.
		while ( --line >= s && isdigit(*line) )
			;	// Back up over preceding digits.
		++line;	// now points to first digit, or beginning of s
		}

	return parse_port(line);
	%}

## Converts the result of the FTP EPSV command (see :rfc:`2428`) to an
## :zeek:type:`ftp_port`.  The format is ``"<text> (<d><d><d><tcp-port><d>)"``,
## where ``<d>`` is a delimiter in the ASCII range 33-126 (usually ``|``).
##
## str: The string containing the result of the FTP EPSV command.
##
## Returns: The FTP PORT, e.g., ``[h=10.0.0.1, p=1055/tcp, valid=T]``.
##
## .. zeek:see:: parse_ftp_port parse_eftp_port parse_ftp_pasv fmt_ftp_port
function parse_ftp_epsv%(str: string%): ftp_port
	%{
	const char* s = str->CheckString();
	const char* line = strchr(s, '(');
	if ( line )
		++line; // move past '('
	return parse_eftp(line);
	%}

## Formats an IP address and TCP port as an FTP PORT command. For example,
## ``10.0.0.1`` and ``1055/tcp`` yields ``"10,0,0,1,4,31"``.
##
## a: The IP address.
##
## p: The TCP port.
##
## Returns: The FTP PORT string.
##
## .. zeek:see:: parse_ftp_port parse_eftp_port parse_ftp_pasv parse_ftp_epsv
function fmt_ftp_port%(a: addr, p: port%): string
	%{
	const uint32_t* addr;
	int len = a->AsAddr().GetBytes(&addr);
	if ( len == 1 )
		{
		uint32_t a = ntohl(addr[0]);
		uint32_t pn = p->Port();
		return zeek::make_intrusive<zeek::StringVal>(zeek::util::fmt("%d,%d,%d,%d,%d,%d",
		                                                 a >> 24, (a >> 16) & 0xff,
		                                                 (a >> 8) & 0xff, a & 0xff,
		                                                 pn >> 8, pn & 0xff));
		}
	else
		{
		zeek::emit_builtin_error("conversion of non-IPv4 address in fmt_ftp_port",
		                         @ARG@[0]);
		return zeek::val_mgr->EmptyString();
		}
	%}
