/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2008-2011. All Rights Reserved.
*
* The contents of this file are subject to the Erlang Public License,
* Version 1.1, (the "License"); you may not use this file except in
* compliance with the License. You should have received a copy of the
* Erlang Public License along with this software. If not, it can be
* retrieved online at http://www.erlang.org/.
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* %CopyrightEnd%
*/
/* A protocol decoder. Simple packet length extraction as well as packet
* body parsing with protocol specific callback interfaces (http and ssl).
*
* Code ripped out from inet_drv.c to also be used by BIF decode_packet.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "packet_parser.h"
#include <ctype.h>
#include "sys.h"
/* #define INET_DRV_DEBUG 1 */
#ifdef INET_DRV_DEBUG
# define DEBUG 1
# undef DEBUGF
# define DEBUGF(X) printf X
#endif
#define get_int24(s) ((((unsigned char*) (s))[0] << 16) | \
(((unsigned char*) (s))[1] << 8) | \
(((unsigned char*) (s))[2]))
#define get_little_int32(s) ((((unsigned char*) (s))[3] << 24) | \
(((unsigned char*) (s))[2] << 16) | \
(((unsigned char*) (s))[1] << 8) | \
(((unsigned char*) (s))[0]))
#if !defined(__WIN32__) && !defined(HAVE_STRNCASECMP)
#define STRNCASECMP my_strncasecmp
static int my_strncasecmp(const char *s1, const char *s2, size_t n)
{
int i;
for (i=0;i<n-1 && s1[i] && s2[i] && toupper(s1[i]) == toupper(s2[i]);++i)
;
return (toupper(s1[i]) - toupper(s2[i]));
}
#else
#define STRNCASECMP strncasecmp
#endif
#define HTTP_HDR_HASH_SIZE 53
#define HTTP_METH_HASH_SIZE 13
#define HTTP_MAX_NAME_LEN 20
static char tspecial[128];
static const char* http_hdr_strings[] = {
"Cache-Control",
"Connection",
"Date",
"Pragma",
"Transfer-Encoding",
"Upgrade",
"Via",
"Accept",
"Accept-Charset",
"Accept-Encoding",
"Accept-Language",
"Authorization",
"From",
"Host",
"If-Modified-Since",
"If-Match",
"If-None-Match",
"If-Range",
"If-Unmodified-Since",
"Max-Forwards",
"Proxy-Authorization",
"Range",
"Referer",
"User-Agent",
"Age",
"Location",
"Proxy-Authenticate",
"Public",
"Retry-After",
"Server",
"Vary",
"Warning",
"Www-Authenticate",
"Allow",
"Content-Base",
"Content-Encoding",
"Content-Language",
"Content-Length",
"Content-Location",
"Content-Md5",
"Content-Range",
"Content-Type",
"Etag",
"Expires",
"Last-Modified",
"Accept-Ranges",
"Set-Cookie",
"Set-Cookie2",
"X-Forwarded-For",
"Cookie",
"Keep-Alive",
"Proxy-Connection",
NULL
};
static const char* http_meth_strings[] = {
"OPTIONS",
"GET",
"HEAD",
"POST",
"PUT",
"DELETE",
"TRACE",
NULL
};
static http_atom_t http_hdr_table[sizeof(http_hdr_strings)/sizeof(char*)];
static http_atom_t http_meth_table[sizeof(http_meth_strings)/sizeof(char*)];
static http_atom_t* http_hdr_hash[HTTP_HDR_HASH_SIZE];
static http_atom_t* http_meth_hash[HTTP_METH_HASH_SIZE];
#define CRNL(ptr) (((ptr)[0] == '\r') && ((ptr)[1] == '\n'))
#define NL(ptr) ((ptr)[0] == '\n')
#define SP(ptr) (((ptr)[0] == ' ') || ((ptr)[0] == '\t'))
#define is_tspecial(x) ((((x) > 32) && ((x) < 128)) ? tspecial[(x)] : 1)
#define hash_update(h,c) do { \
unsigned long __g; \
(h) = ((h) << 4) + (c); \
if ((__g = (h) & 0xf0000000)) { \
(h) ^= (__g >> 24); \
(h) ^= __g; \
} \
} while(0)
static void http_hash_insert(const char* name, http_atom_t* entry,
http_atom_t** hash, int hsize)
{
unsigned long h = 0;
const unsigned char* ptr = (const unsigned char*) name;
int ix;
int len = 0;
while (*ptr != '\0') {
hash_update(h, *ptr);
ptr++;
len++;
}
ix = h % hsize;
entry->next = hash[ix];
entry->h = h;
entry->name = name;
entry->len = len;
entry->atom = driver_mk_atom((char*)name);
hash[ix] = entry;
}
static int http_init(void)
{
int i;
unsigned char* ptr;
for (i = 0; i < 33; i++)
tspecial[i] = 1;
for (i = 33; i < 127; i++)
tspecial[i] = 0;
for (ptr = (unsigned char*)"()<>@,;:\\\"/[]?={} \t"; *ptr != '\0'; ptr++)
tspecial[*ptr] = 1;
for (i = 0; i < HTTP_HDR_HASH_SIZE; i++)
http_hdr_hash[i] = NULL;
for (i = 0; http_hdr_strings[i] != NULL; i++) {
ASSERT(strlen(http_hdr_strings[i]) <= HTTP_MAX_NAME_LEN);
http_hdr_table[i].index = i;
http_hash_insert(http_hdr_strings[i],
&http_hdr_table[i],
http_hdr_hash, HTTP_HDR_HASH_SIZE);
}
for (i = 0; i < HTTP_METH_HASH_SIZE; i++)
http_meth_hash[i] = NULL;
for (i = 0; http_meth_strings[i] != NULL; i++) {
http_meth_table[i].index = i;
http_hash_insert(http_meth_strings[i],
&http_meth_table[i],
http_meth_hash, HTTP_METH_HASH_SIZE);
}
return 0;
}
#define CDR_MAGIC "GIOP"
struct cdr_head {
unsigned char magic[4]; /* 4 bytes must be 'GIOP' */
unsigned char major; /* major version */
unsigned char minor; /* minor version */
unsigned char flags; /* bit 0: 0 == big endian, 1 == little endian
bit 1: 1 == more fragments follow */
unsigned char message_type; /* message type ... */
unsigned char message_size[4]; /* size in (flags bit 0 byte order) */
};
#define TPKT_VRSN 3
struct tpkt_head {
unsigned char vrsn; /* contains TPKT_VRSN */
unsigned char reserved;
unsigned char packet_length[2]; /* size incl header, big-endian (?) */
};
void packet_parser_init()
{
static int done = 0;
if (!done) {
done = 1;
http_init();
}
}
/* Return > 0 Total packet length.in bytes
* = 0 Length unknown, need more data.
* < 0 Error, invalid format.
*/
int packet_get_length(enum PacketParseType htype,
const char* ptr, unsigned n, /* Bytes read so far */
unsigned max_plen, /* Max packet length, 0=no limit */
unsigned trunc_len, /* Truncate (lines) if longer, 0=no limit */
int* statep) /* Protocol specific state */
{
unsigned hlen, plen;
switch (htype) {
case TCP_PB_RAW:
if (n == 0) goto more;
else {
DEBUGF((" => nothing remain packet=%d\r\n", n));
return n;
}
case TCP_PB_1:
/* TCP_PB_1: [L0 | Data] */
hlen = 1;
if (n < hlen) goto more;
plen = get_int8(ptr);
goto remain;
case TCP_PB_2:
/* TCP_PB_2: [L1,L0 | Data] */
hlen = 2;
if (n < hlen) goto more;
plen = get_int16(ptr);
goto remain;
case TCP_PB_4:
/* TCP_PB_4: [L3,L2,L1,L0 | Data] */
hlen = 4;
if (n < hlen) goto more;
plen = get_int32(ptr);
goto remain;
case TCP_PB_RM:
/* TCP_PB_RM: [L3,L2,L1,L0 | Data]
** where MSB (bit) is used to signal end of record
*/
hlen = 4;
if (n < hlen) goto more;
plen = get_int32(ptr) & 0x7fffffff;
goto remain;
case TCP_PB_LINE_LF: {
/* TCP_PB_LINE_LF: [Data ... \n] */
const char* ptr2;
if ((ptr2 = memchr(ptr, '\n', n)) == NULL) {
if (n > max_plen && max_plen != 0) { /* packet full */
DEBUGF((" => packet full (no NL)=%d\r\n", n));
goto error;
}
else if (n >= trunc_len && trunc_len!=0) { /* buffer full */
DEBUGF((" => line buffer full (no NL)=%d\r\n", n));
return trunc_len;
}
goto more;
}
else {
int len = (ptr2 - ptr) + 1; /* including newline */
if (len > max_plen && max_plen!=0) {
DEBUGF((" => packet_size %d exceeded\r\n", max_plen));
goto error;
}
if (len > trunc_len && trunc_len!=0) {
DEBUGF((" => truncated line=%d\r\n", trunc_len));
return trunc_len;
}
DEBUGF((" => nothing remain packet=%d\r\n", len));
return len;
}
}
case TCP_PB_ASN1: {
/* TCP_PB_ASN1: handles long (4 bytes) or short length format */
const char* tptr = ptr;
int length;
int nn = n;
if (n < 2) goto more;
nn--;
if ((*tptr++ & 0x1f) == 0x1f) { /* Long tag format */
while (nn && ((*tptr & 0x80) == 0x80)) {
tptr++;
nn--;
}
if (nn < 2) goto more;
tptr++;
nn--;
}
/* tptr now point to length field and nn characters remain */
length = *tptr & 0x7f;
if ((*tptr & 0x80) == 0x80) { /* Long length format */
tptr++;
nn--;
if (nn < length) goto more;
switch (length) {
case 0: plen = 0; break;
case 1: plen = get_int8(tptr); tptr += 1; break;
case 2: plen = get_int16(tptr); tptr += 2; break;
case 3: plen = get_int24(tptr); tptr += 3; break;
case 4: plen = get_int32(tptr); tptr += 4; break;
default: goto error; /* error */
}
}
else {
tptr++;
plen = length;
}
hlen = (tptr-ptr);
goto remain;
}
case TCP_PB_CDR: {
const struct cdr_head* hp;
hlen = sizeof(struct cdr_head);
if (n < hlen) goto more;
hp = (struct cdr_head*) ptr;
if (sys_memcmp(hp->magic, CDR_MAGIC, 4) != 0)
goto error;
if (hp->flags & 0x01) /* Byte ordering flag */
plen = get_little_int32(hp->message_size);
else
plen = get_int32(hp->message_size);
goto remain;
}
case TCP_PB_FCGI: {
const struct fcgi_head* hp;
hlen = sizeof(struct fcgi_head);
if (n < hlen) goto more;
hp = (struct fcgi_head*) ptr;
if (hp->version != FCGI_VERSION_1)
goto error;
plen = ((hp->contentLengthB1 << 8) | hp->contentLengthB0)
+ hp->paddingLength;
goto remain;
}
case TCP_PB_HTTPH:
case TCP_PB_HTTPH_BIN:
*statep = !0;
case TCP_PB_HTTP:
case TCP_PB_HTTP_BIN:
/* TCP_PB_HTTP: data \r\n(SP data\r\n)* */
plen = n;
if (((plen == 1) && NL(ptr)) || ((plen == 2) && CRNL(ptr)))
goto done;
else {
const char* ptr1 = ptr;
int len = plen;
if (!max_plen) {
/* This is for backward compatibility with old user of decode_packet
* that might use option 'line_length' to limit accepted length of
* http lines.
*/
max_plen = trunc_len;
}
while (1) {
const char* ptr2 = memchr(ptr1, '\n', len);
if (ptr2 == NULL) {
if (max_plen != 0) {
if (n >= max_plen) /* packet full */
goto error;
}
goto more;
}
else {
plen = (ptr2 - ptr) + 1;
if (*statep == 0) {
if (max_plen != 0 && plen > max_plen)
goto error;
goto done;
}
if (plen < n) {
if (SP(ptr2+1) && plen>2) {
/* header field value continue on next line */
ptr1 = ptr2+1;
len = n - plen;
}
else {
if (max_plen != 0 && plen > max_plen)
goto error;
goto done;
}
}
else {
if (max_plen != 0 && plen > max_plen)
goto error;
goto more;
}
}
}
}
case TCP_PB_TPKT: {
const struct tpkt_head* hp;
hlen = sizeof(struct tpkt_head);
if (n < hlen)
goto more;
hp = (struct tpkt_head*) ptr;
if (hp->vrsn == TPKT_VRSN) {
plen = get_int16(hp->packet_length) - hlen;
if (plen < 0)
goto error;
}
else
goto error;
goto remain;
}
case TCP_PB_SSL_TLS:
hlen = 5;
if (n < hlen) goto more;
if ((ptr[0] & 0x80) && ptr[2] == 1) {
/* Ssl-v2 Client hello <<1:1, Len:15, 1:8, Version:16>> */
plen = (get_int16(&ptr[0]) & 0x7fff) - 3;
}
else {
/* <<ContentType:8, Version:16, Length:16>> */
plen = get_int16(&ptr[3]);
}
goto remain;
default:
DEBUGF((" => case error\r\n"));
return -1;
}
more:
return 0;
remain:
{
int tlen = hlen + plen;
if ((max_plen != 0 && plen > max_plen)
|| tlen < (int)hlen) { /* wrap-around protection */
return -1;
}
return tlen;
}
done:
return plen;
error:
return -1;
}
static http_atom_t* http_hash_lookup(const char* name, int len,
unsigned long h,
http_atom_t** hash, int hsize)
{
int ix = h % hsize;
http_atom_t* ap = hash[ix];
while (ap != NULL) {
if ((ap->h == h) && (ap->len == len) &&
(strncmp(ap->name, name, len) == 0))
return ap;
ap = ap->next;
}
return NULL;
}
static void
http_parse_absoluteURI(PacketHttpURI* uri, const char* uri_ptr, int uri_len)
{
const char* p;
if ((p = memchr(uri_ptr, '/', uri_len)) == NULL) {
/* host [":" port] */
uri->s2_ptr = "/";
uri->s2_len = 1;
}
else {
int n = (p - uri_ptr);
uri->s2_ptr = p;
uri->s2_len = uri_len - n;
uri_len = n;
}
uri->s1_ptr = uri_ptr;
uri->port = 0; /* undefined */
/* host[:port] */
if ((p = memchr(uri_ptr, ':', uri_len)) == NULL) {
uri->s1_len = uri_len;
}
else {
int n = (p - uri_ptr);
int port = 0;
uri->s1_len = n;
n = uri_len - (n+1);
p++;
while(n && isdigit((int) *p)) {
port = port*10 + (*p - '0');
n--;
p++;
}
if (n==0 && port!=0)
uri->port = port;
}
}
/*
** Handle URI syntax:
**
** Request-URI = "*" | absoluteURI | abs_path
** absoluteURI = scheme ":" *( uchar | reserved )
** net_path = "//" net_loc [ abs_path ]
** abs_path = "/" rel_path
** rel_path = [ path ] [ ";" params ] [ "?" query ]
** path = fsegment *( "/" segment )
** fsegment = 1*pchar
** segment = *pchar
** params = param *( ";" param )
** param = *( pchar | "/" )
** query = *( uchar | reserved )
**
** http_URL = "http:" "//" host [ ":" port ] [ abs_path ]
**
** host = <A legal Internet host domain name
** or IP address (in dotted-decimal form),
** as defined by Section 2.1 of RFC 1123>
** port = *DIGIT
**
** {absoluteURI, <scheme>, <host>, <port>, <path+params+query>}
** when <scheme> = http | https
** {scheme, <scheme>, <chars>}
** wheb <scheme> is something else then http or https
** {abs_path, <path>}
**
** <string> (unknown form)
**
*/
static void http_parse_uri(PacketHttpURI* uri, const char* uri_ptr, int uri_len)
{
if ((uri_len == 1) && (uri_ptr[0] == '*'))
uri->type = URI_STAR;
else if ((uri_len <= 1) || (uri_ptr[0] == '/')) {
uri->type = URI_ABS_PATH;
uri->s1_ptr = uri_ptr;
uri->s1_len = uri_len;
}
else if ((uri_len>=7) && (STRNCASECMP(uri_ptr, "http://", 7) == 0)) {
uri_len -= 7;
uri_ptr += 7;
uri->type = URI_HTTP;
http_parse_absoluteURI(uri, uri_ptr, uri_len);
}
else if ((uri_len>=8) && (STRNCASECMP(uri_ptr, "https://", 8) == 0)) {
uri_len -= 8;
uri_ptr += 8;
uri->type = URI_HTTPS;
http_parse_absoluteURI(uri, uri_ptr, uri_len);
}
else {
char* ptr;
if ((ptr = memchr(uri_ptr, ':', uri_len)) == NULL) {
uri->type = URI_STRING;
uri->s1_ptr = uri_ptr;
uri->s1_len = uri_len;
}
else {
int slen = ptr - uri_ptr;
uri->type = URI_SCHEME;
uri->s1_ptr = uri_ptr;
uri->s1_len = slen;
uri->s2_ptr = uri_ptr + (slen+1);
uri->s2_len = uri_len - (slen+1);
}
}
}
/*
** parse http message:
** http_eoh - end of headers
** {http_header, Key, Value} - Key = atom() | string()
** {http_request, Method,Url,Version}
** {http_response, Version, Status, Message}
** {http_error, Error-Line}
*/
int packet_parse_http(const char* buf, int len, int* statep,
PacketCallbacks* pcb, void* arg)
{
const char* ptr = buf;
const char* p0;
int n = len;
/* remove trailing CRNL (accept NL as well) */
if ((n >= 2) && (buf[n-2] == '\r'))
n -= 2;
else if ((n >= 1) && (buf[n-1] == '\n'))
n -= 1;
if (*statep == 0) {
/* start-line = Request-Line | Status-Line */
if (n >= 5 && (strncmp(buf, "HTTP/", 5) == 0)) {
int major = 0;
int minor = 0;
int status = 0;
/* Status-Line = HTTP-Version SP
* Status-Code SP Reason-Phrase
* CRNL
* HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
*/
ptr += 5;
n -= 5;
p0 = ptr;
while (n && isdigit((int) *ptr)) {
major = 10*major + (*ptr - '0');
ptr++;
n--;
}
if (ptr==p0 || !n || (*ptr != '.'))
return -1;
ptr++;
n--;
p0 = ptr;
while (n && isdigit((int) *ptr)) {
minor = 10*minor + (*ptr - '0');
ptr++;
n--;
}
if (ptr==p0) return -1;
p0 = ptr;
while (n && SP(ptr)) {
ptr++; n--;
}
if (ptr==p0) return -1;
while (n && isdigit((int) *ptr)) {
status = 10*status + (*ptr - '0');
ptr++;
n--;
}
p0 = ptr;
while (n && SP(ptr)) {
ptr++; n--;
}
if (ptr==p0 && n>0) return -1;
/* NOTE: the syntax allows empty reason phrases */
(*statep) = !0;
return pcb->http_response(arg, major, minor, status,
ptr, n);
}
else {
/* Request-Line = Method SP Request-URI SP HTTP-Version CRLF */
http_atom_t* meth;
const char* meth_ptr = buf;
int meth_len;
PacketHttpURI uri;
const char* uri_ptr;
int uri_len;
int major = 0;
int minor = 0;
unsigned long h = 0;
while (n && !is_tspecial((unsigned char)*ptr)) {
hash_update(h, (int)*ptr);
ptr++;
n--;
}
meth_len = ptr - meth_ptr;
if (n == 0 || meth_len == 0 || !SP(ptr)) return -1;
meth = http_hash_lookup(meth_ptr, meth_len, h,
http_meth_hash, HTTP_METH_HASH_SIZE);
while (n && SP(ptr)) {
ptr++; n--;
}
uri_ptr = ptr;
while (n && !SP(ptr)) {
ptr++; n--;
}
if ((uri_len = (ptr - uri_ptr)) == 0)
return -1;
while (n && SP(ptr)) {
ptr++; n--;
}
if (n == 0) {
(*statep) = !0;
http_parse_uri(&uri, uri_ptr, uri_len);
return pcb->http_request(arg, meth, meth_ptr, meth_len,
&uri, 0, 9);
}
if (n < 8)
return -1;
if (strncmp(ptr, "HTTP/", 5) != 0)
return -1;
ptr += 5;
n -= 5;
p0 = ptr;
while (n && isdigit((int) *ptr)) {
major = 10*major + (*ptr - '0');
ptr++;
n--;
}
if (ptr==p0 || !n || (*ptr != '.'))
return -1;
ptr++;
n--;
p0 = ptr;
while (n && isdigit((int) *ptr)) {
minor = 10*minor + (*ptr - '0');
ptr++;
n--;
}
if (ptr==p0) return -1;
(*statep) = !0;
http_parse_uri(&uri, uri_ptr, uri_len);
return pcb->http_request(arg, meth, meth_ptr, meth_len,
&uri, major, minor);
}
}
else {
int up = 1; /* make next char uppercase */
http_atom_t* name;
char name_buf[HTTP_MAX_NAME_LEN];
const char* name_ptr = name_buf;
int name_len;
unsigned long h;
if (n == 0) {
/* end of headers */
*statep = 0; /* reset state (for next request) */
return pcb->http_eoh(arg);
}
h = 0;
name_len = 0;
while (!is_tspecial((unsigned char)*ptr)) {
if (name_len < HTTP_MAX_NAME_LEN) {
int c = *ptr;
if (up) {
if (islower(c)) {
c = toupper(c);
}
up = 0;
}
else {
if (isupper(c))
c = tolower(c);
else if (c == '-')
up = 1;
}
name_buf[name_len] = c;
hash_update(h, c);
}
name_len++;
ptr++;
if (--n == 0) return -1;
}
while (n && SP(ptr)) { /* Skip white space before ':' */
ptr++; n--;
}
if (*ptr != ':') {
return -1;
}
if (name_len <= HTTP_MAX_NAME_LEN) {
name = http_hash_lookup(name_buf, name_len, h,
http_hdr_hash, HTTP_HDR_HASH_SIZE);
}
else {
/* Is it ok to return original name without case adjustments? */
name_ptr = buf;
name = NULL;
}
ptr++;
n--;
/* Skip white space after ':' */
while (n && SP(ptr)) {
ptr++; n--;
}
return pcb->http_header(arg, name, name_ptr, name_len,
ptr, n);
}
return -1;
}
int packet_parse_ssl(const char* buf, int len,
PacketCallbacks* pcb, void* arg)
{
/* Check for ssl-v2 client hello */
if ((buf[0] & 0x80) && buf[2] == 1) {
unsigned major = (unsigned char) buf[3];
unsigned minor = (unsigned char) buf[4];
char prefix[4];
/* <<1:8,Length:24,Data/binary>> */
prefix[0] = 1;
put_int24(len-3,&prefix[1]);
return pcb->ssl_tls(arg, 22, major, minor, buf+3, len-3, prefix, sizeof(prefix));
}
else {
/* ContentType (1 byte), ProtocolVersion (2 bytes), Length (2 bytes big-endian) */
unsigned type = (unsigned char) buf[0];
unsigned major = (unsigned char) buf[1];
unsigned minor = (unsigned char) buf[2];
return pcb->ssl_tls(arg, type, major, minor, buf+5, len-5, NULL, 0);
}
}