wireshark/epan/dfilter/semcheck.c

739 lines
17 KiB
C

/*
* $Id: semcheck.c,v 1.19 2003/08/27 15:23:04 gram Exp $
*
* Ethereal - Network traffic analyzer
* By Gerald Combs <gerald@ethereal.com>
* Copyright 2001 Gerald Combs
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include "dfilter-int.h"
#include "syntax-tree.h"
#include "sttype-range.h"
#include "sttype-test.h"
#include <epan/exceptions.h>
#include <epan/packet.h>
static void
semcheck(stnode_t *st_node);
typedef gboolean (*FtypeCanFunc)(enum ftenum);
/* Compares to ftenum_t's and decides if they're
* compatible or not (if they're the same basic type) */
static gboolean
compatible_ftypes(ftenum_t a, ftenum_t b)
{
switch (a) {
case FT_NONE:
case FT_PROTOCOL:
case FT_FLOAT: /* XXX - should be able to compare with INT */
case FT_DOUBLE: /* XXX - should be able to compare with INT */
case FT_ABSOLUTE_TIME:
case FT_RELATIVE_TIME:
case FT_IPv4:
case FT_IPv6:
case FT_IPXNET:
case FT_INT64: /* XXX - should be able to compare with INT */
case FT_UINT64: /* XXX - should be able to compare with INT */
return a == b;
case FT_ETHER:
case FT_BYTES:
case FT_UINT_BYTES:
return (b == FT_ETHER || b == FT_BYTES || b == FT_UINT_BYTES);
case FT_BOOLEAN:
case FT_FRAMENUM:
case FT_UINT8:
case FT_UINT16:
case FT_UINT24:
case FT_UINT32:
case FT_INT8:
case FT_INT16:
case FT_INT24:
case FT_INT32:
switch (b) {
case FT_BOOLEAN:
case FT_FRAMENUM:
case FT_UINT8:
case FT_UINT16:
case FT_UINT24:
case FT_UINT32:
case FT_INT8:
case FT_INT16:
case FT_INT24:
case FT_INT32:
return TRUE;
default:
return FALSE;
}
case FT_STRING:
case FT_STRINGZ:
case FT_UINT_STRING:
switch (b) {
case FT_STRING:
case FT_STRINGZ:
case FT_UINT_STRING:
return TRUE;
default:
return FALSE;
}
case FT_NUM_TYPES:
g_assert_not_reached();
}
g_assert_not_reached();
return FALSE;
}
/* Creates a FT_UINT32 fvalue with a given value. */
static fvalue_t*
mk_uint32_fvalue(guint32 val)
{
fvalue_t *fv;
fv = fvalue_new(FT_UINT32);
fvalue_set_integer(fv, val);
return fv;
}
/* Try to make an fvalue from a string using a value_string or true_false_string.
* This works only for ftypes that are integers. Returns the created fvalue_t*
* or NULL if impossible. */
static fvalue_t*
mk_fvalue_from_val_string(header_field_info *hfinfo, char *s)
{
static const true_false_string default_tf = { "True", "False" };
const true_false_string *tf = &default_tf;
const value_string *vals;
/* Early return? */
switch(hfinfo->type) {
case FT_NONE:
case FT_PROTOCOL:
case FT_FLOAT:
case FT_DOUBLE:
case FT_ABSOLUTE_TIME:
case FT_RELATIVE_TIME:
case FT_IPv4:
case FT_IPv6:
case FT_IPXNET:
case FT_ETHER:
case FT_BYTES:
case FT_UINT_BYTES:
case FT_STRING:
case FT_STRINGZ:
case FT_UINT_STRING:
case FT_UINT64:
case FT_INT64:
return FALSE;
case FT_BOOLEAN:
case FT_FRAMENUM:
case FT_UINT8:
case FT_UINT16:
case FT_UINT24:
case FT_UINT32:
case FT_INT8:
case FT_INT16:
case FT_INT24:
case FT_INT32:
break;
case FT_NUM_TYPES:
g_assert_not_reached();
}
/* Reset the dfilter error message, since *something* interesting
* will happen, and the error message will be more interesting than
* any error message I happen to have now. */
dfilter_error_msg = NULL;
/* TRUE/FALSE *always* exist for FT_BOOLEAN. */
if (hfinfo->type == FT_BOOLEAN) {
if (hfinfo->strings) {
tf = hfinfo->strings;
}
if (strcasecmp(s, tf->true_string) == 0) {
return mk_uint32_fvalue(TRUE);
}
else if (strcasecmp(s, tf->false_string) == 0) {
return mk_uint32_fvalue(FALSE);
}
else {
dfilter_fail("\"%s\" cannot be found among the possible values for %s.",
s, hfinfo->abbrev);
return NULL;
}
}
/* Do val_strings exist? */
if (!hfinfo->strings) {
dfilter_fail("%s cannot accept strings as values.",
hfinfo->abbrev);
return FALSE;
}
vals = hfinfo->strings;
while (vals->strptr != NULL) {
if (strcasecmp(s, vals->strptr) == 0) {
return mk_uint32_fvalue(vals->value);
}
vals++;
}
dfilter_fail("\"%s\" cannot be found among the possible values for %s.",
s, hfinfo->abbrev);
return FALSE;
}
static gboolean
is_bytes_type(enum ftenum type)
{
switch(type) {
case FT_ETHER:
case FT_BYTES:
case FT_UINT_BYTES:
case FT_IPv6:
return TRUE;
case FT_NONE:
case FT_PROTOCOL:
case FT_FLOAT:
case FT_DOUBLE:
case FT_ABSOLUTE_TIME:
case FT_RELATIVE_TIME:
case FT_IPv4:
case FT_IPXNET:
case FT_STRING:
case FT_STRINGZ:
case FT_UINT_STRING:
case FT_BOOLEAN:
case FT_FRAMENUM:
case FT_UINT8:
case FT_UINT16:
case FT_UINT24:
case FT_UINT32:
case FT_UINT64:
case FT_INT8:
case FT_INT16:
case FT_INT24:
case FT_INT32:
case FT_INT64:
return FALSE;
case FT_NUM_TYPES:
g_assert_not_reached();
}
g_assert_not_reached();
return FALSE;
}
/* If the LHS of a relation test is a FIELD, run some checks
* and possibly some modifications of syntax tree nodes. */
static void
check_relation_LHS_FIELD(const char *relation_string, FtypeCanFunc can_func,
gboolean allow_partial_value,
stnode_t *st_node, stnode_t *st_arg1, stnode_t *st_arg2)
{
stnode_t *new_st;
sttype_id_t type1, type2;
header_field_info *hfinfo1, *hfinfo2;
ftenum_t ftype1, ftype2;
fvalue_t *fvalue;
char *s;
drange_node *rn;
type1 = stnode_type_id(st_arg1);
type2 = stnode_type_id(st_arg2);
hfinfo1 = stnode_data(st_arg1);
ftype1 = hfinfo1->type;
if (!can_func(ftype1)) {
dfilter_fail("%s (type=%s) cannot participate in '%s' comparison.",
hfinfo1->abbrev, ftype_pretty_name(ftype1),
relation_string);
THROW(TypeError);
}
if (type2 == STTYPE_FIELD) {
hfinfo2 = stnode_data(st_arg2);
ftype2 = hfinfo2->type;
if (!compatible_ftypes(ftype1, ftype2)) {
dfilter_fail("%s and %s are not of compatible types.",
hfinfo1->abbrev, hfinfo2->abbrev);
THROW(TypeError);
}
/* Do this check even though you'd think that if
* they're compatible, then can_func() would pass. */
if (!can_func(ftype2)) {
dfilter_fail("%s (type=%s) cannot participate in specified comparison.",
hfinfo2->abbrev, ftype_pretty_name(ftype2));
THROW(TypeError);
}
}
else if (type2 == STTYPE_STRING) {
s = stnode_data(st_arg2);
fvalue = fvalue_from_string(ftype1, s, dfilter_fail);
if (!fvalue) {
/* check value_string */
fvalue = mk_fvalue_from_val_string(hfinfo1, s);
if (!fvalue) {
THROW(TypeError);
}
}
new_st = stnode_new(STTYPE_FVALUE, fvalue);
sttype_test_set2_args(st_node, st_arg1, new_st);
stnode_free(st_arg2);
}
else if (type2 == STTYPE_UNPARSED) {
s = stnode_data(st_arg2);
fvalue = fvalue_from_unparsed(ftype1, s, allow_partial_value, dfilter_fail);
if (!fvalue) {
/* check value_string */
fvalue = mk_fvalue_from_val_string(hfinfo1, s);
if (!fvalue) {
THROW(TypeError);
}
}
new_st = stnode_new(STTYPE_FVALUE, fvalue);
sttype_test_set2_args(st_node, st_arg1, new_st);
stnode_free(st_arg2);
}
else if (type2 == STTYPE_RANGE) {
if (!is_bytes_type(ftype1)) {
if (!ftype_can_slice(ftype1)) {
dfilter_fail("\"%s\" is a %s and cannot be converted into a sequence of bytes.",
hfinfo1->abbrev,
ftype_pretty_name(ftype1));
THROW(TypeError);
}
/* Convert entire field to bytes */
new_st = stnode_new(STTYPE_RANGE, NULL);
rn = drange_node_new();
drange_node_set_start_offset(rn, 0);
drange_node_set_to_the_end(rn);
/* st_arg1 is freed in this step */
sttype_range_set1(new_st, st_arg1, rn);
sttype_test_set2_args(st_node, new_st, st_arg2);
}
}
else {
g_assert_not_reached();
}
}
static void
check_relation_LHS_STRING(FtypeCanFunc can_func _U_, gboolean allow_partial_value _U_,
stnode_t *st_node,
stnode_t *st_arg1, stnode_t *st_arg2)
{
stnode_t *new_st;
sttype_id_t type1, type2;
header_field_info *hfinfo2;
ftenum_t ftype2;
fvalue_t *fvalue;
char *s;
type1 = stnode_type_id(st_arg1);
type2 = stnode_type_id(st_arg2);
if (type2 == STTYPE_FIELD) {
hfinfo2 = stnode_data(st_arg2);
ftype2 = hfinfo2->type;
s = stnode_data(st_arg1);
fvalue = fvalue_from_string(ftype2, s, dfilter_fail);
if (!fvalue) {
/* check value_string */
fvalue = mk_fvalue_from_val_string(hfinfo2, s);
if (!fvalue) {
THROW(TypeError);
}
}
new_st = stnode_new(STTYPE_FVALUE, fvalue);
sttype_test_set2_args(st_node, new_st, st_arg2);
stnode_free(st_arg1);
}
else if (type2 == STTYPE_STRING || type2 == STTYPE_UNPARSED) {
/* Well now that's silly... */
dfilter_fail("Neither \"%s\" nor \"%s\" are field or protocol names.",
stnode_data(st_arg1),
stnode_data(st_arg2));
THROW(TypeError);
}
else if (type2 == STTYPE_RANGE) {
s = stnode_data(st_arg1);
fvalue = fvalue_from_string(FT_BYTES, s, dfilter_fail);
if (!fvalue) {
THROW(TypeError);
}
new_st = stnode_new(STTYPE_FVALUE, fvalue);
sttype_test_set2_args(st_node, new_st, st_arg2);
stnode_free(st_arg1);
}
else {
g_assert_not_reached();
}
}
static void
check_relation_LHS_UNPARSED(FtypeCanFunc can_func _U_, gboolean allow_partial_value,
stnode_t *st_node,
stnode_t *st_arg1, stnode_t *st_arg2)
{
stnode_t *new_st;
sttype_id_t type1, type2;
header_field_info *hfinfo2;
ftenum_t ftype2;
fvalue_t *fvalue;
char *s;
type1 = stnode_type_id(st_arg1);
type2 = stnode_type_id(st_arg2);
if (type2 == STTYPE_FIELD) {
hfinfo2 = stnode_data(st_arg2);
ftype2 = hfinfo2->type;
s = stnode_data(st_arg1);
fvalue = fvalue_from_unparsed(ftype2, s, allow_partial_value, dfilter_fail);
if (!fvalue) {
/* check value_string */
fvalue = mk_fvalue_from_val_string(hfinfo2, s);
if (!fvalue) {
THROW(TypeError);
}
}
new_st = stnode_new(STTYPE_FVALUE, fvalue);
sttype_test_set2_args(st_node, new_st, st_arg2);
stnode_free(st_arg1);
}
else if (type2 == STTYPE_STRING || type2 == STTYPE_UNPARSED) {
/* Well now that's silly... */
dfilter_fail("Neither \"%s\" nor \"%s\" are field or protocol names.",
stnode_data(st_arg1),
stnode_data(st_arg2));
THROW(TypeError);
}
else if (type2 == STTYPE_RANGE) {
/* XXX - is this right? */
s = stnode_data(st_arg1);
fvalue = fvalue_from_unparsed(FT_BYTES, s, allow_partial_value, dfilter_fail);
if (!fvalue) {
THROW(TypeError);
}
new_st = stnode_new(STTYPE_FVALUE, fvalue);
sttype_test_set2_args(st_node, new_st, st_arg2);
stnode_free(st_arg1);
}
else {
g_assert_not_reached();
}
}
struct check_drange_sanity_args {
drange_node* err_node;
};
static void
check_drange_node_sanity(gpointer data, gpointer user_data)
{
drange_node* drnode = data;
struct check_drange_sanity_args *args = user_data;
gint start_offset, end_offset;
switch (drange_node_get_ending(drnode)) {
case UNINITIALIZED:
g_assert_not_reached();
break;
case LENGTH:
/*
* Any sanity checks required here?
*/
break;
case OFFSET:
/*
* Make sure the start offset isn't beyond the end
* offset.
*/
start_offset = drange_node_get_start_offset(drnode);
end_offset = drange_node_get_end_offset(drnode);
if (start_offset > end_offset) {
if (args->err_node == NULL)
args->err_node = drnode;
}
break;
case TO_THE_END:
break;
}
}
static void
check_drange_sanity(stnode_t *st)
{
struct check_drange_sanity_args args;
header_field_info *hfinfo;
gint start_offset, end_offset;
args.err_node = NULL;
drange_foreach_drange_node(sttype_range_drange(st),
check_drange_node_sanity, &args);
if (args.err_node != NULL) {
hfinfo = sttype_range_hfinfo(st);
start_offset = drange_node_get_start_offset(args.err_node);
end_offset = drange_node_get_end_offset(args.err_node);
dfilter_fail("Range %d-%d specified for \"%s\" isn't valid, as %d is greater than %d",
start_offset, end_offset,
hfinfo->abbrev,
start_offset, end_offset);
THROW(TypeError);
}
}
static void
check_relation_LHS_RANGE(FtypeCanFunc can_func _U_, gboolean allow_partial_value,
stnode_t *st_node,
stnode_t *st_arg1, stnode_t *st_arg2)
{
stnode_t *new_st;
sttype_id_t type1, type2;
header_field_info *hfinfo1, *hfinfo2;
ftenum_t ftype1, ftype2;
fvalue_t *fvalue;
char *s;
drange_node *rn;
type1 = stnode_type_id(st_arg1);
type2 = stnode_type_id(st_arg2);
hfinfo1 = sttype_range_hfinfo(st_arg1);
ftype1 = hfinfo1->type;
if (!ftype_can_slice(ftype1)) {
dfilter_fail("\"%s\" is a %s and cannot be sliced into a sequence of bytes.",
hfinfo1->abbrev, ftype_pretty_name(ftype1));
THROW(TypeError);
}
check_drange_sanity(st_arg1);
if (type2 == STTYPE_FIELD) {
hfinfo2 = sttype_range_hfinfo(st_arg2);
ftype2 = hfinfo2->type;
if (!is_bytes_type(ftype2)) {
if (!ftype_can_slice(ftype2)) {
dfilter_fail("\"%s\" is a %s and cannot be converted into a sequence of bytes.",
hfinfo2->abbrev,
ftype_pretty_name(ftype2));
THROW(TypeError);
}
/* Convert entire field to bytes */
new_st = stnode_new(STTYPE_RANGE, NULL);
rn = drange_node_new();
drange_node_set_start_offset(rn, 0);
drange_node_set_to_the_end(rn);
/* st_arg2 is freed in this step */
sttype_range_set1(new_st, st_arg2, rn);
sttype_test_set2_args(st_node, st_arg1, new_st);
}
}
else if (type2 == STTYPE_STRING) {
s = stnode_data(st_arg2);
fvalue = fvalue_from_string(FT_BYTES, s, dfilter_fail);
if (!fvalue) {
THROW(TypeError);
}
new_st = stnode_new(STTYPE_FVALUE, fvalue);
sttype_test_set2_args(st_node, st_arg1, new_st);
stnode_free(st_arg2);
}
else if (type2 == STTYPE_UNPARSED) {
s = stnode_data(st_arg2);
fvalue = fvalue_from_unparsed(FT_BYTES, s, allow_partial_value, dfilter_fail);
if (!fvalue) {
THROW(TypeError);
}
new_st = stnode_new(STTYPE_FVALUE, fvalue);
sttype_test_set2_args(st_node, st_arg1, new_st);
stnode_free(st_arg2);
}
else if (type2 == STTYPE_RANGE) {
check_drange_sanity(st_arg2);
/* XXX - check lengths of both ranges */
}
else {
g_assert_not_reached();
}
}
/* Check the semantics of any relational test. */
static void
check_relation(const char *relation_string, gboolean allow_partial_value,
FtypeCanFunc can_func, stnode_t *st_node,
stnode_t *st_arg1, stnode_t *st_arg2)
{
switch (stnode_type_id(st_arg1)) {
case STTYPE_FIELD:
check_relation_LHS_FIELD(relation_string, can_func,
allow_partial_value, st_node, st_arg1, st_arg2);
break;
case STTYPE_STRING:
check_relation_LHS_STRING(can_func,
allow_partial_value, st_node, st_arg1, st_arg2);
break;
case STTYPE_RANGE:
check_relation_LHS_RANGE(can_func,
allow_partial_value, st_node, st_arg1, st_arg2);
break;
case STTYPE_UNPARSED:
check_relation_LHS_UNPARSED(can_func,
allow_partial_value, st_node, st_arg1, st_arg2);
break;
case STTYPE_UNINITIALIZED:
case STTYPE_TEST:
case STTYPE_INTEGER:
case STTYPE_FVALUE:
case STTYPE_NUM_TYPES:
g_assert_not_reached();
}
}
/* Check the semantics of any type of TEST */
static void
check_test(stnode_t *st_node)
{
test_op_t st_op;
stnode_t *st_arg1, *st_arg2;
sttype_test_get(st_node, &st_op, &st_arg1, &st_arg2);
switch (st_op) {
case TEST_OP_UNINITIALIZED:
g_assert_not_reached();
break;
case TEST_OP_EXISTS:
/* nothing */
break;
case TEST_OP_NOT:
semcheck(st_arg1);
break;
case TEST_OP_AND:
case TEST_OP_OR:
semcheck(st_arg1);
semcheck(st_arg2);
break;
case TEST_OP_EQ:
check_relation("==", FALSE, ftype_can_eq, st_node, st_arg1, st_arg2);
break;
case TEST_OP_NE:
check_relation("!=", FALSE, ftype_can_ne, st_node, st_arg1, st_arg2);
break;
case TEST_OP_GT:
check_relation(">", FALSE, ftype_can_gt, st_node, st_arg1, st_arg2);
break;
case TEST_OP_GE:
check_relation(">=", FALSE, ftype_can_ge, st_node, st_arg1, st_arg2);
break;
case TEST_OP_LT:
check_relation("<", FALSE, ftype_can_lt, st_node, st_arg1, st_arg2);
break;
case TEST_OP_LE:
check_relation("<=", FALSE, ftype_can_le, st_node, st_arg1, st_arg2);
break;
case TEST_OP_CONTAINS:
check_relation("contains", TRUE, ftype_can_contains, st_node, st_arg1, st_arg2);
break;
default:
g_assert_not_reached();
}
}
/* Check the entire syntax tree. */
static void
semcheck(stnode_t *st_node)
{
/* The parser assures that the top-most syntax-tree
* node will be a TEST node, no matter what. So assert that. */
switch (stnode_type_id(st_node)) {
case STTYPE_TEST:
check_test(st_node);
break;
default:
g_assert_not_reached();
}
}
/* Check the syntax tree for semantic errors, and convert
* some of the nodes into the form they need to be in order to
* later generate the DFVM bytecode. */
gboolean
dfw_semcheck(dfwork_t *dfw)
{
/* Instead of having to check for errors at every stage of
* the semantic-checking, the semantic-checking code will
* throw an exception if a problem is found. */
TRY {
semcheck(dfw->st_root);
}
CATCH(TypeError) {
return FALSE;
}
ENDTRY;
return TRUE;
}