diff options
-rw-r--r-- | docbook/CMakeLists.txt | 1 | ||||
-rw-r--r-- | docbook/Makefile.common | 1 | ||||
-rwxr-xr-x | docbook/make-wsluarm.pl | 8 | ||||
-rw-r--r-- | docbook/user-guide.xml | 1 | ||||
-rw-r--r-- | docbook/wsluarm.xml | 1 | ||||
-rw-r--r-- | epan/wslua/CMakeLists.txt | 1 | ||||
-rw-r--r-- | epan/wslua/Makefile.am | 2 | ||||
-rw-r--r-- | epan/wslua/Makefile.nmake | 2 | ||||
-rwxr-xr-x | epan/wslua/make-init-lua.pl | 20 | ||||
-rw-r--r-- | epan/wslua/template-init.lua | 4 | ||||
-rw-r--r-- | epan/wslua/wslua.h | 101 | ||||
-rw-r--r-- | epan/wslua/wslua_file.c | 2134 | ||||
-rw-r--r-- | epan/wslua/wslua_util.c | 21 | ||||
-rw-r--r-- | test/captures/sip.pcapng | bin | 0 -> 3696 bytes | |||
-rw-r--r-- | test/captures/sipmsg.log | 136 | ||||
-rw-r--r-- | test/lua/acme_file.lua | 1365 | ||||
-rw-r--r-- | test/lua/pcap_file.lua | 548 | ||||
-rwxr-xr-x | test/suite-wslua.sh | 98 | ||||
-rw-r--r-- | wiretap/file_access.c | 496 | ||||
-rw-r--r-- | wiretap/file_wrappers.c | 47 | ||||
-rw-r--r-- | wiretap/file_wrappers.h | 5 | ||||
-rw-r--r-- | wiretap/wtap-int.h | 13 | ||||
-rw-r--r-- | wiretap/wtap.h | 40 |
23 files changed, 4874 insertions, 171 deletions
diff --git a/docbook/CMakeLists.txt b/docbook/CMakeLists.txt index 0373da5f76..e03076f5aa 100644 --- a/docbook/CMakeLists.txt +++ b/docbook/CMakeLists.txt @@ -309,6 +309,7 @@ set(WSLUA_MODULES ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_proto.c ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_tree.c ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_tvb.c + ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_file.c ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_util.c ${CMAKE_SOURCE_DIR}/epan/wslua/wslua_struct.c ) diff --git a/docbook/Makefile.common b/docbook/Makefile.common index adb4d49d9a..d0056cc3e3 100644 --- a/docbook/Makefile.common +++ b/docbook/Makefile.common @@ -254,4 +254,5 @@ WSLUA_MODULES = \ ../epan/wslua/wslua_struct.c \ ../epan/wslua/wslua_tree.c \ ../epan/wslua/wslua_tvb.c \ + ../epan/wslua/wslua_file.c \ ../epan/wslua/wslua_util.c diff --git a/docbook/make-wsluarm.pl b/docbook/make-wsluarm.pl index 5c0b8db740..c4a06d21ff 100755 --- a/docbook/make-wsluarm.pl +++ b/docbook/make-wsluarm.pl @@ -40,8 +40,16 @@ sub gorolla { my $s = shift; $s =~ s/^([\n]|\s)*//ms; $s =~ s/([\n]|\s)*$//ms; + # as far as I can tell, these will only convert the *first* '<'/'>' they find in a line, but + # not subsequent ones in that line (because the flag isn't 'msg'?) -hadriel $s =~ s/\</</ms; $s =~ s/\>/>/ms; + # this is a horrible horrible hack, but it works + # basically we undo the replacements just made above, if it's a '</para>' or '<para>' case + # so that comments can include them for prettier output. Really this API generator thing needs + # to be rewritten, but I don't understand perl well enough to do it properly -hadriel + $s =~ s/<\/para>/<\/para>/ms; + $s =~ s/<para>/<para>/ms; $s; } diff --git a/docbook/user-guide.xml b/docbook/user-guide.xml index dd35a85ed4..5d5e2548c6 100644 --- a/docbook/user-guide.xml +++ b/docbook/user-guide.xml @@ -346,6 +346,7 @@ WSLua Reference Manual <!ENTITY WsLuaProto SYSTEM "wsluarm_src/wslua_proto.xml"> <!ENTITY WsLuaTree SYSTEM "wsluarm_src/wslua_tree.xml"> <!ENTITY WsLuaTvb SYSTEM "wsluarm_src/wslua_tvb.xml"> + <!ENTITY WsLuaFile SYSTEM "wsluarm_src/wslua_file.xml"> <!ENTITY WsLuaUtility SYSTEM "wsluarm_src/wslua_util.xml"> <!ENTITY WsLuaInt64 SYSTEM "wsluarm_src/wslua_int64.xml"> <!ENTITY WsLuaStruct SYSTEM "wsluarm_src/wslua_struct.xml"> diff --git a/docbook/wsluarm.xml b/docbook/wsluarm.xml index 1a01b568d4..5c9253da52 100644 --- a/docbook/wsluarm.xml +++ b/docbook/wsluarm.xml @@ -176,6 +176,7 @@ end &WsLuaProto; &WsLuaTree; &WsLuaTvb; + &WsLuaFile; &WsLuaUtility; &WsLuaInt64; &WsLuaStruct; diff --git a/epan/wslua/CMakeLists.txt b/epan/wslua/CMakeLists.txt index 7d29798665..ca5dfa3fb9 100644 --- a/epan/wslua/CMakeLists.txt +++ b/epan/wslua/CMakeLists.txt @@ -37,6 +37,7 @@ set(WSLUA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/wslua/wslua_gui.c ${CMAKE_CURRENT_SOURCE_DIR}/wslua/wslua_util.c ${CMAKE_CURRENT_SOURCE_DIR}/wslua/wslua_field.c + ${CMAKE_CURRENT_SOURCE_DIR}/wslua/wslua_file.c ${CMAKE_CURRENT_SOURCE_DIR}/wslua/wslua_struct.c ${CMAKE_CURRENT_SOURCE_DIR}/wslua/wslua_dumper.c ${CMAKE_CURRENT_SOURCE_DIR}/wslua/wslua_internals.c diff --git a/epan/wslua/Makefile.am b/epan/wslua/Makefile.am index 0a721bd4fe..96c455e2dc 100644 --- a/epan/wslua/Makefile.am +++ b/epan/wslua/Makefile.am @@ -43,6 +43,7 @@ wslua_modules = \ $(srcdir)/wslua_gui.c \ $(srcdir)/wslua_util.c \ $(srcdir)/wslua_field.c \ + $(srcdir)/wslua_file.c \ $(srcdir)/wslua_struct.c \ $(srcdir)/wslua_dumper.c \ $(srcdir)/wslua_internals.c @@ -134,6 +135,7 @@ checkapi: lrexlib_glib_f.c \ wslua_dumper.c \ wslua_field.c \ + wslua_file.c \ wslua_gui.c \ wslua_int64.c \ wslua_pinfo.c \ diff --git a/epan/wslua/Makefile.nmake b/epan/wslua/Makefile.nmake index aa3e07a038..dc672efe47 100644 --- a/epan/wslua/Makefile.nmake +++ b/epan/wslua/Makefile.nmake @@ -28,6 +28,7 @@ MODULES = \ wslua_gui.c \ wslua_util.c \ wslua_field.c \ + wslua_file.c \ wslua_struct.c \ wslua_dumper.c \ wslua_internals.c @@ -49,6 +50,7 @@ OBJECTS= \ wslua_gui.obj \ wslua_util.obj \ wslua_field.obj \ + wslua_file.obj \ wslua_struct.obj \ wslua_dumper.obj \ wslua_internals.obj diff --git a/epan/wslua/make-init-lua.pl b/epan/wslua/make-init-lua.pl index 89cfdb5231..048f8097d2 100755 --- a/epan/wslua/make-init-lua.pl +++ b/epan/wslua/make-init-lua.pl @@ -34,7 +34,9 @@ die "'$WSROOT' is not a directory" unless -d $WSROOT; my $wtap_encaps_table = ''; my $wtap_filetypes_table = ''; +my $wtap_commenttypes_table = ''; my $ft_types_table = ''; +my $wtap_presence_flags_table = ''; my $bases_table = ''; my $encodings = ''; my $expert_pi = ''; @@ -43,7 +45,9 @@ my $menu_groups = ''; my %replacements = %{{ WTAP_ENCAPS => \$wtap_encaps_table, WTAP_FILETYPES => \$wtap_filetypes_table, + WTAP_COMMENTTYPES => \$wtap_commenttypes_table, FT_TYPES => \$ft_types_table, + WTAP_PRESENCE_FLAGS => \$wtap_presence_flags_table, BASES => \$bases_table, ENCODINGS => \$encodings, EXPERT => \$expert_pi, @@ -66,10 +70,13 @@ close TEMPLATE; # # WTAP_FILE_ values # WTAP_ENCAP_ values +# WTAP_HAS_ values # $wtap_encaps_table = "-- Wiretap encapsulations XXX\nwtap_encaps = {\n"; $wtap_filetypes_table = "-- Wiretap file types\nwtap_filetypes = {\n"; +$wtap_commenttypes_table = "-- Wiretap file comment types\nwtap_comments = {\n"; +$wtap_presence_flags_table = "-- Wiretap presence flags\nwtap_presence_flags = {\n"; open WTAP_H, "< $WSROOT/wiretap/wtap.h" or die "cannot open '$WSROOT/wiretap/wtap.h': $!"; @@ -82,10 +89,23 @@ while(<WTAP_H>) { if ( /^#define WTAP_FILE_(?:TYPE_SUBTYPE_)?([A-Z0-9_]+)\s+(\d+)/ ) { $wtap_filetypes_table .= "\t[\"$1\"] = $2,\n"; } + + if ( /^#define WTAP_COMMENT_([A-Z0-9_]+)\s+(0x\d+)/ ) { + $wtap_commenttypes_table .= "\t[\"$1\"] = $2,\n"; + } + + if ( /^#define WTAP_HAS_([A-Z0-9_]+)\s+(0x\d+)\s+\/\*\*<([^\*]+)\*\// ) { + my $num = hex($2); + $wtap_presence_flags_table .= "\t[\"$1\"] = $num, --$3\n"; + } } $wtap_encaps_table =~ s/,\n$/\n}\nwtap = wtap_encaps -- for bw compatibility\n/msi; $wtap_filetypes_table =~ s/,\n$/\n}\n/msi; +$wtap_commenttypes_table =~ s/,\n$/\n}\n/msi; +# wtap_presence_flags_table has comments at the end (not a comma), +# but Lua doesn't care about extra commas so leave it in +$wtap_presence_flags_table =~ s/\n$/\n}\n/msi; # # Extract values from epan/ftypes/ftypes.h: diff --git a/epan/wslua/template-init.lua b/epan/wslua/template-init.lua index f036c2460a..a80dc74023 100644 --- a/epan/wslua/template-init.lua +++ b/epan/wslua/template-init.lua @@ -73,8 +73,12 @@ end -- %WTAP_FILETYPES% +-- %WTAP_COMMENTTYPES% + -- %FT_TYPES% +-- %WTAP_PRESENCE_FLAGS% + -- %BASES% -- %ENCODINGS% diff --git a/epan/wslua/wslua.h b/epan/wslua/wslua.h index 840c61a28c..4bc16062dc 100644 --- a/epan/wslua/wslua.h +++ b/epan/wslua/wslua.h @@ -226,15 +226,59 @@ struct _wslua_tap { gboolean all_fields; }; -# define DIRECTORY_T GDir -# define FILE_T gchar -# define OPENDIR_OP(name) g_dir_open(name, 0, dir->dummy) -# define DIRGETNEXT_OP(dir) g_dir_read_name(dir) -# define GETFNAME_OP(file) (file); -# define CLOSEDIR_OP(dir) g_dir_close(dir) +/* a "File" object can be different things under the hood. It can either + be a FILE_T from wtap struct, which it is during read operations, or it + can be a wtap_dumper struct during write operations. A wtap_dumper struct + has a FILE_T member, but we can't only store its pointer here because + dump operations need the whole thing to write out with. Ugh. */ +struct _wslua_file { + FILE_T file; + wtap_dumper *wdh; /* will be NULL during read usage */ + gboolean expired; +}; + +/* a "CaptureInfo" object can also be different things under the hood. */ +struct _wslua_captureinfo { + wtap *wth; /* will be NULL during write usage */ + wtap_dumper *wdh; /* will be NULL during read usage */ + gboolean expired; +}; + +struct _wslua_phdr { + struct wtap_pkthdr *phdr; /* this also exists in wtap struct, but is different for seek_read ops */ + Buffer *buf; /* can't use the one in wtap because it's different for seek_read ops */ + gboolean expired; +}; + +struct _wslua_const_phdr { + const struct wtap_pkthdr *phdr; + const guint8 *pd; + gboolean expired; +}; + +struct _wslua_filehandler { + struct file_type_subtype_info finfo; + gboolean is_reader; + gboolean is_writer; + gchar* description; + gchar* type; + gchar* extensions; + lua_State* L; + int read_open_ref; + int read_ref; + int seek_read_ref; + int read_close_ref; + int seq_read_close_ref; + int can_write_encap_ref; + int write_open_ref; + int write_ref; + int write_close_ref; + int file_type; + gboolean registered; +}; struct _wslua_dir { - DIRECTORY_T* dir; + GDir* dir; char* ext; GError** dummy; }; @@ -272,6 +316,12 @@ typedef struct _wslua_field_info* FieldInfo; typedef struct _wslua_tap* Listener; typedef struct _wslua_tw* TextWindow; typedef struct _wslua_progdlg* ProgDlg; +typedef struct _wslua_file* File; +typedef struct _wslua_captureinfo* CaptureInfo; +typedef struct _wslua_captureinfo* CaptureInfoConst; +typedef struct _wslua_phdr* FrameInfo; +typedef struct _wslua_const_phdr* FrameInfoConst; +typedef struct _wslua_filehandler* FileHandler; typedef wtap_dumper* Dumper; typedef struct lua_pseudo_header* PseudoHeader; typedef tvbparse_t* Parser; @@ -446,6 +496,9 @@ extern int wslua_set__index(lua_State *L); C obj = check##C (L,1); \ if (! lua_isfunction(L,-1) ) \ return luaL_error(L, "%s's attribute `%s' must be a function", #C , #field ); \ + if (obj->field##_ref != LUA_NOREF) \ + /* there was one registered before, remove it */ \ + luaL_unref(L, LUA_REGISTRYINDEX, obj->field##_ref); \ obj->field##_ref = luaL_ref(L, LUA_REGISTRYINDEX); \ return 0; \ } \ @@ -491,6 +544,13 @@ extern int wslua_set__index(lua_State *L); /* silly little trick so we can add a semicolon after this macro */ \ static int C##_set_##name(lua_State*) +#define WSLUA_ATTRIBUTE_NAMED_BOOLEAN_SETTER(C,name,member) \ + WSLUA_ATTRIBUTE_SET(C,name, { \ + if (! lua_isboolean(L,-1) ) \ + return luaL_error(L, "%s's attribute `%s' must be a boolean", #C , #name ); \ + obj->member = lua_toboolean(L,-1); \ + }) + /* to make this integral-safe, we treat it as int32 and then cast Note: this will truncate 64-bit integers (but then Lua itself only has doubles */ #define WSLUA_ATTRIBUTE_NAMED_NUMBER_SETTER(C,name,member,cast) \ @@ -503,6 +563,23 @@ extern int wslua_set__index(lua_State *L); #define WSLUA_ATTRIBUTE_NUMBER_SETTER(C,member,cast) \ WSLUA_ATTRIBUTE_NAMED_NUMBER_SETTER(C,member,member,cast) +#define WSLUA_ATTRIBUTE_NAMED_STRING_SETTER(C,field,member,need_free) \ + static int C##_set_##field (lua_State* L) { \ + C obj = check##C (L,1); \ + gchar* s = NULL; \ + if (lua_isstring(L,-1) || lua_isnil(L,-1)) { \ + s = g_strdup(lua_tostring(L,-1)); \ + } else { \ + return luaL_error(L, "%s's attribute `%s' must be a string or nil", #C , #field ); \ + } \ + if (obj->member != NULL && need_free) \ + free((void*) obj->member); \ + obj->member = s; \ + return 0; \ + } + +#define WSLUA_ATTRIBUTE_STRING_SETTER(C,field,need_free) \ + WSLUA_ATTRIBUTE_NAMED_STRING_SETTER(C,field,field,need_free) #define WSLUA_ERROR(name,error) { luaL_error(L, ep_strdup_printf("%s%s", #name ": " ,error) ); } #define WSLUA_ARG_ERROR(name,attr,error) { luaL_argerror(L,WSLUA_ARG_ ## name ## _ ## attr, #name ": " error); } @@ -518,7 +595,17 @@ extern int wslua_set__index(lua_State *L); /* empty macro arguments trigger ISO C90 warnings, so do this */ #define NOP (void)p + #define FAIL_ON_NULL(s) if (! *p) luaL_argerror(L,idx,"null " s) + +#define FAIL_ON_NULL_MEMBER_OR_EXPIRED(s,member) if (!*p) { \ + luaL_argerror(L,idx,"null " s); \ + } else if ((*p)->member == NULL) { \ + luaL_argerror(L,idx,"null " s " member " #member); \ + } else if ((*p)->expired) { \ + luaL_argerror(L,idx,"expired " s); \ + } + #define FAIL_ON_NULL_OR_EXPIRED(s) if (!*p) { \ luaL_argerror(L,idx,"null " s); \ } else if ((*p)->expired) { \ diff --git a/epan/wslua/wslua_file.c b/epan/wslua/wslua_file.c new file mode 100644 index 0000000000..cc3ae668d5 --- /dev/null +++ b/epan/wslua/wslua_file.c @@ -0,0 +1,2134 @@ +/* + * wslua_file.c + * + * Wireshark's interface to the Lua Programming Language + * for custom file format reading/writing. + * + * (c) 2014, Hadriel Kaplan <hadrielk@yahoo.com> + * + * $Id: wslua_file.c 47903 2013-02-26 15:10:28Z hadrielk $ + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include "wslua.h" +#include <errno.h> +#include <wiretap/wtap-int.h> +#include <wiretap/file_wrappers.h> +#include <epan/addr_resolv.h> +#include <math.h> + +#define MAX_LINE_LENGTH 65536 + +/* WSLUA_MODULE File Custom file format reading/writing */ + + +WSLUA_CLASS_DEFINE(File,FAIL_ON_NULL_OR_EXPIRED("File"),NOP); +/* + A File object, passed into Lua as an argument by FileHandler callback + functions (e.g., read_open, read, write, etc.). This behaves similarly to the + Lua 'io' library's 'file' object, returned when calling io.open(), *except* + in this case you cannot call file:close(), file:open(), nor file:setvbuf(), + since Wireshark/tshark manages the opening and closing of files. + You also cannot use the 'io' library itself on this object, i.e. you cannot + do io.read(file, 4). Instead, use this File with the object-oriented style + calling its methods, i.e. myfile:read(4). + + </para><para> + The purpose of this object is to hide the internal complexity of how Wireshark + handles files, and instead provide a Lua interface that is familiar, by mimicking + the io library. The reason true/raw io files cannot be used is because Wireshark + does many things under the hood, such as compress the file, or write to stdout, + or various other things based on configuration/commands. + + </para><para> + When a File object is passed in through reading-based callback functions, such as + read_open(), read(), and read_close(), then the File object's write() and flush() + functions are not usable and will raise an error if used. + + </para><para> + When a File object is passed in through writing-based callback functions, such as + write_open(), write(), and write_close(), then the File object's read() and lines() + functions are not usable and will raise an error if used. + + </para><para> + Note: a File object should never be stored/saved beyond the scope of the callback function + it is passed in to. + */ + +/* a "File" object can be different things under the hood. It can either + be a FILE_T from wtap struct, which it is during read operations, or it + can be a wtap_dumper struct during write operations. A wtap_dumper struct + has a FILE_T member, but we can't only store its pointer here because + dump operations need the whole thing to write out with. Ugh. */ +static File* push_File(lua_State* L, FILE_T ft) { + File f = (File) g_malloc(sizeof(struct _wslua_file)); + f->file = ft; + f->wdh = NULL; + f->expired = FALSE; + return pushFile(L,f); +} + +static File* push_Wdh(lua_State* L, wtap_dumper *wdh) { + File f = (File) g_malloc(sizeof(struct _wslua_file)); + f->file = wdh->fh; + f->wdh = wdh; + f->expired = FALSE; + return pushFile(L,f); +} + +static gboolean file_is_reader(File f) { + return (f->wdh == NULL); +} + +/* This internal function reads a number from the file, similar to Lua's io.read("*num"). + * In Lua this is done with a fscanf(file, "%lf", &double), but we can't use fscanf() since + * this may be coming from a zip file and we need to use file_wrappers.c functions. + * So we get a character at a time, building a buffer for fscanf. + * XXX this isn't perfect - if just "2." exists in file, for example, it consumes it. + */ +#define WSLUA_MAXNUMBER2STR 32 /* 16 digits, sign, point, and \0 */ +static int File_read_number (lua_State *L, FILE_T ft) { + lua_Number d; + gchar buff[WSLUA_MAXNUMBER2STR]; + int buff_end = 0; + int c = -1; + int num_digits = 0; + gboolean has_decimal = FALSE; + + c = file_peekc(ft); + if (c == '+' || c == '-') { + buff[buff_end++] = (gchar)c; + /* make sure next char is a digit */ + c = file_peekc(ft); + if (c < '0' || c > '9') { + lua_pushnil(L); /* "result" to be removed */ + return 0; /* read fails */ + } + /* eat the +/- */ + file_getc(ft); + } + + while((c = file_peekc(ft)) > 0 && buff_end < (WSLUA_MAXNUMBER2STR-1)) { + if (c >= '0' && c <= '9') { + buff[buff_end++] = (gchar)c; + num_digits++; + file_getc(ft); + } + else if (!has_decimal && c == '.') { + has_decimal = TRUE; + buff[buff_end++] = (gchar)c; + file_getc(ft); + } + else break; + } + + buff[buff_end] = '\0'; + + if (buff_end > 0 && num_digits > 0 && sscanf(buff, "%lf", &d) == 1) { + lua_pushnumber(L, d); + return 1; + } + else { + lua_pushnil(L); /* "result" to be removed */ + return 0; /* read fails */ + } +} + +static int File_read_line(lua_State *L, FILE_T ft) { + static gchar linebuff[MAX_LINE_LENGTH]; + gint64 pos_before = file_tell(ft); + gint length = 0; + + if (file_gets(linebuff, MAX_LINE_LENGTH, ft) == NULL) { + /* No characters found, or error */ + /* *err = file_error(ft, err_info); */ + return 0; + } + + /* Set length (avoiding strlen()) */ + length = (gint)(file_tell(ft) - pos_before); + + /* ...but don't want to include newline in line length */ + if (linebuff[length-1] == '\n') { + length--; + /* Nor do we want '\r' (as will be written when log is created on windows) */ + if (linebuff[length-1] == '\r') { + length--; + } + linebuff[length] = '\0'; + } + + lua_pushlstring(L, linebuff, length); + return 1; +} + +/* This internal function reads X number of btyes form the file, same as io.read(num) in Lua. + * Since we have to use file_wrappers.c, and an intermediate buffer, we read it in chunks + * of 1024 bytes at a time. (or less if called with a smaller number) To do that, we use + * Lua's buffer manager to push it into Lua as those chunks, while ending up with one long + * Lua string in the end. + */ +#define WSLUA_BUFFERSIZE 1024 + +/* Lua 5.1 used lua_objlen() instead of lua_rawlen() */ +#if LUA_VERSION_NUM == 501 +#define lua_rawlen lua_objlen +#endif + +static int File_read_chars(lua_State *L, FILE_T ft, size_t n) { + size_t rlen; /* how much to read */ + size_t nr; /* number of chars actually read */ + int nri; /* temp number of chars read, as an int to handle -1 errors */ + gchar buff[WSLUA_BUFFERSIZE]; /* for file_read to write to, and we push into Lua */ + luaL_Buffer b; + + rlen = WSLUA_BUFFERSIZE; /* try to read that much each time */ + luaL_buffinit(L, &b); /* initialize Lua buffer */ + + do { + if (rlen > n) rlen = n; /* cannot read more than asked */ + nri = file_read(buff, (unsigned int)rlen, ft); + if (nri < 1) break; + nr = (size_t) nri; + luaL_addlstring(&b, buff, nr); + n -= nr; /* still have to read `n' chars */ + } while (n > 0 && nr == rlen); /* until end of count or eof */ + + luaL_pushresult(&b); /* close buffer */ + + return (n == 0 || lua_rawlen(L, -1) > 0); +} + +/* returns nil if EOF, else an empty string - this is what Lua does too for this case */ +static int File_test_eof(lua_State *L, FILE_T ft) { + if (file_eof(ft)) { + lua_pushnil(L); + } + else { + lua_pushlstring(L, "", 0); + } + return 1; +} + +static int pushresult (lua_State *L, int i, const char *filename) { + int en = errno; /* calls to Lua API may change this value, so we save it */ + if (i) { + lua_pushboolean(L, 1); + return 1; + } + else { + lua_pushnil(L); + if (filename) + lua_pushfstring(L, "%s: %s", filename, strerror(en)); + else + lua_pushfstring(L, "%s", strerror(en)); + lua_pushinteger(L, en); + return 3; + } +} + +WSLUA_METHOD File_read(lua_State* L) { + /* Reads from the File, similar to Lua's file:read(). See Lua 5.x ref manual for file:read(). */ + File f = shiftFile(L,1); + int nargs = lua_gettop(L); + int success; + int n = 1; + FILE_T ft = NULL; + + if (!f || !f->file) { + return 0; + } + + /* shiftFile() doesn't verify things like expired */ + if (f->expired) { + g_warning("Error in File read: Lua File has expired"); + return 0; + } + + if (!file_is_reader(f)) { + g_warning("Error in File read: this File object instance is for writing only"); + return 0; + } + + ft = f->file; + + /* file_clearerr(ft); */ + if (nargs == 0) { /* no arguments? */ + success = File_read_line(L, ft); + n = 2; /* to return 1 result */ + } + else { /* ensure stack space for all results and Lua */ + luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments"); + success = 1; + for (n = 1; nargs-- && success; n++) { + if (lua_type(L, n) == LUA_TNUMBER) { + size_t l = (size_t)lua_tointeger(L, n); + success = (l == 0) ? File_test_eof(L, ft) : File_read_chars(L, ft, l); + } + else { + const char *p = lua_tostring(L, n); + if (!p) return luaL_argerror(L, n, "invalid format argument"); + luaL_argcheck(L, p[0] == '*', n, "invalid option"); + switch (p[1]) { + case 'n': /* number */ + success = File_read_number(L, ft); + break; + case 'l': /* line */ + success = File_read_line(L, ft); + break; + case 'a': /* file, read everything */ + File_read_chars(L, ft, ~((size_t)0)); /* read MAX_SIZE_T chars */ + success = 1; /* always success */ + break; + default: + return luaL_argerror(L, n, "invalid format"); + } + } + } + } + if (file_error(ft, NULL)) + return pushresult(L, 0, NULL); + if (!success) { + lua_pop(L, 1); /* remove last result */ + lua_pushnil(L); /* push nil instead */ + } + return n - 1; +} + +WSLUA_METHOD File_seek(lua_State* L) { + /* Seeks in the File, similar to Lua's file:seek(). See Lua 5.x ref manual for file:seek(). */ + static const int mode[] = { SEEK_SET, SEEK_CUR, SEEK_END }; + static const char *const modenames[] = {"set", "cur", "end", NULL}; + File f = checkFile(L,1); + int op = luaL_checkoption(L, 2, "cur", modenames); + gint64 offset = (gint64) luaL_optlong(L, 3, 0); + int err = WTAP_ERR_INTERNAL; + + + if (file_is_reader(f)) { + offset = file_seek(f->file, offset, mode[op], &err); + + if (offset < 0) { + lua_pushnil(L); /* error */ + lua_pushstring(L, wtap_strerror(err)); + return 2; + } + + lua_pushnumber(L, (lua_Number)(file_tell(f->file))); + } + else { + offset = wtap_dump_file_seek(f->wdh, offset, mode[op], &err); + + if (offset < 0) { + lua_pushnil(L); /* error */ + lua_pushstring(L, wtap_strerror(err)); + return 2; + } + + offset = wtap_dump_file_tell(f->wdh, &err); + + if (offset < 0) { + lua_pushnil(L); /* error */ + lua_pushstring(L, wtap_strerror(err)); + return 2; + } + + lua_pushnumber(L, (lua_Number)(offset)); + } + + WSLUA_RETURN(1); /* The current file cursor position as a number. */ +} + +static int File_lines_iterator(lua_State* L) { + FILE_T ft = *(FILE_T *)lua_touserdata(L, lua_upvalueindex(1)); + int success; + + if (ft == NULL) + return luaL_error(L, "Error getting File handle for lines iterator"); + + success = File_read_line(L, ft); + + /* if (ferror(ft)) + return luaL_error(L, "%s", strerror(errno)); + */ + return success; +} + +WSLUA_METHOD File_lines(lua_State* L) { + /* Lua iterator function for retrieving ascii File lines, similar to Lua's file:lines(). See Lua 5.x ref manual for file:lines(). */ + File f = checkFile(L,1); + FILE_T ft = NULL; + + if (!f->file) + return luaL_error(L, "Error getting File handle for lines"); + + if (!file_is_reader(f)) { + g_warning("Error in File read: this File object instance is for writing only"); + return 0; + } + + ft = f->file; + + lua_pushlightuserdata(L, ft); + lua_pushcclosure(L, File_lines_iterator, 1); + + return 1; +} + +/* yeah this function is a little weird, but I'm mimicking Lua's actual code for io:write() */ +WSLUA_METHOD File_write(lua_State* L) { + /* Writes to the File, similar to Lua's file:write(). See Lua 5.x ref manual for file:write(). */ + File f = checkFile(L,1); + int arg = 2; /* beginning index for arguments */ + int nargs = lua_gettop(L) - 1; + int status = TRUE; + int err = 0; + + if (!f->wdh) { + g_warning("Error in File read: this File object instance is for reading only"); + return 0; + } + + lua_pushvalue(L, 1); /* push File at the stack top (to be returned) */ + + for (; nargs--; arg++) { + size_t len; + const char *s = luaL_checklstring(L, arg, &len); + status = wtap_dump_file_write(f->wdh, s, len, &err); + if (!status) break; + f->wdh->bytes_dumped += len; + } + + if (!status) { + lua_pop(L,1); /* pop the extraneous File object */ + lua_pushnil(L); + lua_pushfstring(L, "File write error: %s", strerror(err)); + lua_pushinteger(L, err); + return 3; + } + + return 1; /* File object already on stack top */ +} + +WSLUA_METAMETHOD File__tostring(lua_State* L) { + /* Generates a string of debug info for the File object */ + File f = toFile(L,1); + + if (!f) { + lua_pushstring(L,"File pointer is NULL!"); + } else { + lua_pushfstring(L,"File expired=%s, handle=%s, is %s", f->expired? "true":"false", f->file? "<ptr>":"<NULL>", + f->wdh? "writer":"reader"); + } + + WSLUA_RETURN(1); /* String of debug information. */ +} + +/* We free the struct we malloc'ed, but not the FILE_T/dumper in it of course */ +static int File__gc(lua_State* L _U_) { + File f = toFile(L,1); + if (f) + g_free(f); + return 0; +} + +/* WSLUA_ATTRIBUTE File_compressed RO Whether the File is compressed or not. + See 'wtap_encaps' in init.lua for available types. Set to 'wtap_encaps.PER_PACKET' if packets can + have different types, then later set 'FrameInfo.encap' for each packet during read()/seek_read(). */ +static int File_get_compressed(lua_State* L) { + File f = checkFile(L,1); + + if (file_is_reader(f)) { + lua_pushboolean(L, file_iscompressed(f->file)); + } else { + lua_pushboolean(L, f->wdh->compressed); + } + return 1; +} + +WSLUA_ATTRIBUTES File_attributes[] = { + WSLUA_ATTRIBUTE_ROREG(File,compressed), + { NULL, NULL, NULL } +}; + +WSLUA_METHODS File_methods[] = { + WSLUA_CLASS_FNREG(File,lines), + WSLUA_CLASS_FNREG(File,read), + WSLUA_CLASS_FNREG(File,seek), + WSLUA_CLASS_FNREG(File,write), + { NULL, NULL } +}; + +WSLUA_META File_meta[] = { + WSLUA_CLASS_MTREG(File,tostring), + { NULL, NULL } +}; + +int File_register(lua_State* L) { + WSLUA_REGISTER_CLASS(File); + WSLUA_REGISTER_ATTRIBUTES(File); + return 0; +} + + +WSLUA_CLASS_DEFINE(CaptureInfo,FAIL_ON_NULL_MEMBER_OR_EXPIRED("CaptureInfo",wth),NOP); +/* + A CaptureInfo object, passed into Lua as an argument by FileHandler callback + function read_open(). + This object represents capture file data and meta-data (data about the + capture file) being read into Wireshark/Tshark. + + </para><para> + This object's fields can be written-to by Lua during the read_open function callback. + In other words, when the Lua plugin's FileHandler read_open function is invoked, a + CaptureInfo object will be passed in as one of the arguments, and its fields + should be written to by your Lua code to tell Wireshark about the capture. + */ + +static CaptureInfo* push_CaptureInfo(lua_State* L, wtap *wth) { + CaptureInfo f = (CaptureInfo) g_malloc0(sizeof(struct _wslua_captureinfo)); + f->wth = wth; + f->wdh = NULL; + /* XXX: need to do this? */ + wth->file_encap = WTAP_ENCAP_UNKNOWN; + wth->tsprecision = WTAP_FILE_TSPREC_SEC; + wth->snapshot_length = 0; + f->expired = FALSE; + return pushCaptureInfo(L,f); +} + +WSLUA_METAMETHOD CaptureInfo__tostring(lua_State* L) { + /* Generates a string of debug info for the CaptureInfo */ + CaptureInfo fi = toCaptureInfo(L,1); + + if (!fi || !fi->wth) { + lua_pushstring(L,"CaptureInfo pointer is NULL!"); + } else { + wtap *wth = fi->wth; + lua_pushfstring(L, "CaptureInfo: file_type_subtype=%d, snapshot_length=%d, pkt_encap=%d, tsprecision='%s'", + wth->file_type_subtype, wth->snapshot_length, wth->phdr.pkt_encap, wth->tsprecision); + } + + WSLUA_RETURN(1); /* String of debug information. */ +} + + +static int CaptureInfo__gc(lua_State* L _U_) { + FrameInfo fi = toFrameInfo(L,1); + if (fi) + g_free(fi); + return 0; +} + +/* WSLUA_ATTRIBUTE CaptureInfo_encap RW The packet encapsulation type for the whole file. + See 'wtap_encaps' in init.lua for available types. Set to 'wtap_encaps.PER_PACKET' if packets can + have different types, then later set 'FrameInfo.encap' for each packet during read()/seek_read(). */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(CaptureInfo,encap,wth->file_encap); +WSLUA_ATTRIBUTE_NAMED_NUMBER_SETTER(CaptureInfo,encap,wth->file_encap,int); + +/* WSLUA_ATTRIBUTE CaptureInfo_time_precision RW The precision of the packet timestamps in the file. + See 'wtap_file_tsprec' in init.lua for available precisions. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(CaptureInfo,time_precision,wth->tsprecision); +WSLUA_ATTRIBUTE_NAMED_NUMBER_SETTER(CaptureInfo,time_precision,wth->tsprecision,int); + +/* WSLUA_ATTRIBUTE CaptureInfo_snapshot_length RW The maximum packet length that could be recorded. + Setting it to 0 means unknown. Wireshark cannot handle anything bigger than 65535 bytes. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(CaptureInfo,snapshot_length,wth->snapshot_length); +WSLUA_ATTRIBUTE_NAMED_NUMBER_SETTER(CaptureInfo,snapshot_length,wth->snapshot_length,guint); + +/* WSLUA_ATTRIBUTE CaptureInfo_comment RW A string comment for the whole capture file, + or nil if there is no comment. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(CaptureInfo,comment,wth->shb_hdr.opt_comment); +WSLUA_ATTRIBUTE_NAMED_STRING_SETTER(CaptureInfo,comment,wth->shb_hdr.opt_comment,TRUE); + +/* WSLUA_ATTRIBUTE CaptureInfo_hardware RW A string containing the description of + the hardware used to create the capture, or nil if there is no hardware string. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(CaptureInfo,hardware,wth->shb_hdr.shb_hardware); +WSLUA_ATTRIBUTE_NAMED_STRING_SETTER(CaptureInfo,hardware,wth->shb_hdr.shb_hardware,TRUE); + +/* WSLUA_ATTRIBUTE CaptureInfo_os RW A string containing the name of + the operating system used to create the capture, or nil if there is no os string. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(CaptureInfo,os,wth->shb_hdr.shb_os); +WSLUA_ATTRIBUTE_NAMED_STRING_SETTER(CaptureInfo,os,wth->shb_hdr.shb_os,TRUE); + +/* WSLUA_ATTRIBUTE CaptureInfo_user_app RW A string containing the name of + the application used to create the capture, or nil if there is no user_app string. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(CaptureInfo,user_app,wth->shb_hdr.shb_user_appl); +WSLUA_ATTRIBUTE_NAMED_STRING_SETTER(CaptureInfo,user_app,wth->shb_hdr.shb_user_appl,TRUE); + +/* WSLUA_ATTRIBUTE CaptureInfo_hosts WO Sets resolved ip-to-hostname information. + The value set must be a Lua table of two key-ed names: 'ipv4_addresses' and 'ipv6_addresses'. + The value of each of these names are themselves array tables, of key-ed tables, such that the inner table has a key + 'addr' set to the raw 4-byte or 16-byte IP address Lua string and a 'name' set to the resolved name. + + </para><para> + For example, if the capture file identifies one resolved IPv4 address of 1.2.3.4 to 'foo.com', then you must set + CaptureInfo.hosts to a table of { ipv4_addresses = { { addr = '\01\02\03\04', name = 'foo.com' } } }. + + </para><para> + Note that either the 'ipv4_addresses' or the 'ipv6_addresses' table, or both, may be empty or nil. + */ +static int CaptureInfo_set_hosts(lua_State* L) { + CaptureInfo fi = checkCaptureInfo(L,1); + wtap *wth = fi->wth; + const char *addr = NULL; + const char *name = NULL; + size_t addr_len = 0; + size_t name_len = 0; + guint32 v4_addr = 0; + struct e_in6_addr v6_addr = { {0} }; + + if (!wth->add_new_ipv4 || !wth->add_new_ipv6) { + return luaL_error(L, "CaptureInfo wtap has no IPv4 or IPv6 name resolution"); + } + + if (!lua_istable(L,-1)) { + return luaL_error(L, "CaptureInfo.host must be set to a table"); + } + + /* get the ipv4_addresses table */ + lua_getfield(L, -1, "ipv4_addresses"); + + if (lua_istable(L,-1)) { + /* now walk the table */ + lua_pushnil(L); /* first key */ + while (lua_next(L, -2) != 0) { + /* 'key' (at index -2) and 'value' (at index -1) */ + if (!lua_istable(L,-1)) { + lua_pop(L, 3); /* remove whatever it is, the key, and the ipv4_addreses table */ + return luaL_error(L, "CaptureInfo.host ipv4_addresses table does not contain a table"); + } + + lua_getfield(L, -1, "addr"); + if (!lua_isstring(L,-1)) { + lua_pop(L, 3); /* remove whatever it is, the key, and the ipv4_addreses table */ + return luaL_error(L, "CaptureInfo.host ipv4_addresses table's table does not contain an 'addr' field"); + } + addr = luaL_checklstring(L,-1,&addr_len); + if (addr_len != 4) { + lua_pop(L, 3); /* remove whatever it is, the key, and the ipv4_addreses table */ + return luaL_error(L, "CaptureInfo.host ipv4_addresses 'addr' value is not 4 bytes long"); + } + memcpy(&v4_addr, addr, 4); + + lua_getfield(L, -1, "name"); + if (!lua_isstring(L,-1)) { + lua_pop(L, 3); /* remove whatever it is, the key, and the ipv4_addreses table */ + return luaL_error(L, "CaptureInfo.host ipv4_addresses table's table does not contain an 'addr' field"); + } + name = luaL_checklstring(L,-1,&name_len); + + wth->add_new_ipv4(v4_addr, name); + + /* removes 'value'; keeps 'key' for next iteration */ + lua_pop(L, 1); + } + } + + /* wasn't a table, or it was and we walked it; either way pop it */ + lua_pop(L,1); + + + /* get the ipv6_addresses table */ + lua_getfield(L, -1, "ip6_addresses"); + + if (lua_istable(L,-1)) { + /* now walk the table */ + lua_pushnil(L); /* first key */ + while (lua_next(L, -2) != 0) { + /* 'key' (at index -2) and 'value' (at index -1) */ + if (!lua_istable(L,-1)) { + lua_pop(L, 3); /* remove whatever it is, the key, and the ipv4_addreses table */ + return luaL_error(L, "CaptureInfo.host ipv6_addresses table does not contain a table"); + } + + lua_getfield(L, -1, "addr"); + if (!lua_isstring(L,-1)) { + lua_pop(L, 3); /* remove whatever it is, the key, and the ipv4_addreses table */ + return luaL_error(L, "CaptureInfo.host ipv6_addresses table's table does not contain an 'addr' field"); + } + addr = luaL_checklstring(L,-1,&addr_len); + if (addr_len != 16) { + lua_pop(L, 3); /* remove whatever it is, the key, and the ipv4_addreses table */ + return luaL_error(L, "CaptureInfo.host ipv6_addresses 'addr' value is not 16 bytes long"); + } + memcpy(&v6_addr, addr, 16); + + lua_getfield(L, -1, "name"); + if (!lua_isstring(L,-1)) { + lua_pop(L, 3); /* remove whatever it is, the key, and the ipv4_addreses table */ + return luaL_error(L, "CaptureInfo.host ipv6_addresses table's table does not contain an 'addr' field"); + } + name = luaL_checklstring(L,-1,&name_len); + + wth->add_new_ipv6((const void *)(&v6_addr), name); + + /* removes 'value'; keeps 'key' for next iteration */ + lua_pop(L, 1); + } + } + + /* wasn't a table, or it was and we walked it; either way pop it */ + lua_pop(L,1); + + return 0; +} + +WSLUA_ATTRIBUTES CaptureInfo_attributes[] = { + WSLUA_ATTRIBUTE_RWREG(CaptureInfo,encap), + WSLUA_ATTRIBUTE_RWREG(CaptureInfo,time_precision), + WSLUA_ATTRIBUTE_RWREG(CaptureInfo,snapshot_length), + WSLUA_ATTRIBUTE_RWREG(CaptureInfo,comment), + WSLUA_ATTRIBUTE_RWREG(CaptureInfo,hardware), + WSLUA_ATTRIBUTE_RWREG(CaptureInfo,os), + WSLUA_ATTRIBUTE_RWREG(CaptureInfo,user_app), + WSLUA_ATTRIBUTE_WOREG(CaptureInfo,hosts), + { NULL, NULL, NULL } +}; + +WSLUA_META CaptureInfo_meta[] = { + {"__tostring", CaptureInfo__tostring}, + { NULL, NULL } +}; + +int CaptureInfo_register(lua_State* L) { + WSLUA_REGISTER_META(CaptureInfo); + WSLUA_REGISTER_ATTRIBUTES(CaptureInfo); + return 0; +} + + +WSLUA_CLASS_DEFINE(CaptureInfoConst,FAIL_ON_NULL_MEMBER_OR_EXPIRED("CaptureInfoConst",wdh),NOP); +/* + A CaptureInfoConst object, passed into Lua as an argument to the FileHandler callback + function write_open(). + This object represents capture file data and meta-data (data about the + capture file) for the current capture in Wireshark/Tshark. + + </para><para> + This object's fields are read-from when used by write_open function callback. + In other words, when the Lua plugin's FileHandler write_open function is invoked, a + CaptureInfoConst object will be passed in as one of the arguments, and its fields + should be read from by your Lua code to get data about the capture that needs to be written. + */ + +static CaptureInfoConst* push_CaptureInfoConst(lua_State* L, wtap_dumper *wdh) { + CaptureInfoConst f = (CaptureInfoConst) g_malloc0(sizeof(struct _wslua_captureinfo)); + f->wth = NULL; + f->wdh = wdh; + f->expired = FALSE; + return pushCaptureInfoConst(L,f); +} + +WSLUA_METAMETHOD CaptureInfoConst__tostring(lua_State* L) { + /* Generates a string of debug info for the CaptureInfoConst */ + CaptureInfoConst fi = toCaptureInfoConst(L,1); + + if (!fi || !fi->wdh) { + lua_pushstring(L,"CaptureInfoConst pointer is NULL!"); + } else { + wtap_dumper *wdh = fi->wdh; + lua_pushfstring(L, "CaptureInfoConst: file_type_subtype=%d, snaplen=%d, encap=%d, compressed=%d, tsprecision='%s'", + wdh->file_type_subtype, wdh->snaplen, wdh->encap, wdh->compressed, wdh->tsprecision); + } + + WSLUA_RETURN(1); /* String of debug information. */ +} + +/* WSLUA_ATTRIBUTE CaptureInfoConst_type RO The file type. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(CaptureInfoConst,type,wdh->file_type_subtype); + +/* WSLUA_ATTRIBUTE CaptureInfoConst_snapshot_length RO The maximum packet length that is actually recorded (vs. the original + length of any given packet on-the-wire). A value of 0 means the snapshot length is unknown or there is no one + such length for the whole file. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(CaptureInfoConst,snapshot_length,wdh->snaplen); + +/* WSLUA_ATTRIBUTE CaptureInfoConst_encap RO The packet encapsulation type for the whole file. + See 'wtap_encaps' in init.lua for available types. It is set to 'wtap_encaps.PER_PACKET' if packets can + have different types, in which case each Frame identifies its type, in 'FrameInfo.packet_encap'. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(CaptureInfoConst,encap,wdh->encap); + +/* WSLUA_ATTRIBUTE CaptureInfoConst_comment RW A comment for the whole capture file, if the + 'wtap_presence_flags.COMMENTS' was set in the presence flags; nil if there is no comment. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(CaptureInfoConst,comment,wth->shb_hdr.opt_comment); + +/* WSLUA_ATTRIBUTE CaptureInfoConst_hardware RO A string containing the description of + the hardware used to create the capture, or nil if there is no hardware string. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(CaptureInfoConst,hardware,wth->shb_hdr.shb_hardware); + +/* WSLUA_ATTRIBUTE CaptureInfoConst_os RO A string containing the name of + the operating system used to create the capture, or nil if there is no os string. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(CaptureInfoConst,os,wth->shb_hdr.shb_os); + +/* WSLUA_ATTRIBUTE CaptureInfoConst_user_app RO A string containing the name of + the application used to create the capture, or nil if there is no user_app string. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(CaptureInfoConst,user_app,wth->shb_hdr.shb_user_appl); + +/* WSLUA_ATTRIBUTE CaptureInfoConst_hosts RO A ip-to-hostname Lua table of two key-ed names: 'ipv4_addresses' and 'ipv6_addresses'. + The value of each of these names are themselves array tables, of key-ed tables, such that the inner table has a key + 'addr' set to the raw 4-byte or 16-byte IP address Lua string and a 'name' set to the resolved name. + + </para><para> + For example, if the current capture has one resolved IPv4 address of 1.2.3.4 to 'foo.com', then getting + CaptureInfoConst.hosts will get a table of: + { ipv4_addresses = { { addr = '\01\02\03\04', name = 'foo.com' } }, ipv6_addresses = { } }. + + </para><para> + Note that either the 'ipv4_addresses' or the 'ipv6_addresses' table, or both, may be empty, however they will not + be nil. */ +static int CaptureInfoConst_get_hosts(lua_State* L) { + CaptureInfoConst fi = checkCaptureInfoConst(L,1); + wtap_dumper *wdh = fi->wdh; + + /* create the main table to return */ + lua_newtable(L); + + /* create the ipv4_addresses table */ + lua_newtable(L); + + if (wdh->addrinfo_lists && wdh->addrinfo_lists->ipv4_addr_list) { + hashipv4_t *ipv4_hash_list_entry = (hashipv4_t *)g_list_nth_data(wdh->addrinfo_lists->ipv4_addr_list, 0); + int i, j; + for (i=1, j=1; ipv4_hash_list_entry != NULL; i++) { + if ((ipv4_hash_list_entry->flags & USED_AND_RESOLVED_MASK) == RESOLVED_ADDRESS_USED) { + lua_pushnumber(L, j); /* push numeric index key starting at 1, so it will be an array table */ + /* create the entry table */ + lua_newtable(L); + /* addr is in network order already */ + lua_pushlstring(L, (char*)(&ipv4_hash_list_entry->ip), 4); + lua_setfield(L, -2, "addr"); + lua_pushstring(L, ipv4_hash_list_entry->name); + lua_setfield(L, -2, "name"); + /* now our ipv4_addresses table is at -3, key number is -2, and entry table at -2, so we're good */ + lua_settable(L, -3); + j++; + } + ipv4_hash_list_entry = (hashipv4_t *)g_list_nth_data(wdh->addrinfo_lists->ipv4_addr_list, i); + } + } + + /* set the (possibly empty) ipv4_addresses table into the main table */ + lua_setfield(L, -2, "ipv4_addresses"); + + /* create the ipv6_addresses table */ + lua_newtable(L); + + if (wdh->addrinfo_lists && wdh->addrinfo_lists->ipv6_addr_list) { + hashipv6_t *ipv6_hash_list_entry = (hashipv6_t *)g_list_nth_data(wdh->addrinfo_lists->ipv6_addr_list, 0); + int i, j; + for (i=1, j=1; ipv6_hash_list_entry != NULL; i++) { + if ((ipv6_hash_list_entry->flags & USED_AND_RESOLVED_MASK) == RESOLVED_ADDRESS_USED) { + lua_pushnumber(L, j); /* push numeric index key starting at 1, so it will be an array table */ + /* create the entry table */ + lua_newtable(L); + /* addr is in network order already */ + lua_pushlstring(L, (char*)(&ipv6_hash_list_entry->addr.bytes[0]), 16); + lua_setfield(L, -2, "addr"); + lua_pushstring(L, ipv6_hash_list_entry->name); + lua_setfield(L, -2, "name"); + /* now our ipv6_addresses table is at -3, key number is -2, and entry table at -2, so we're good */ + lua_settable(L, -3); + j++; + } + ipv6_hash_list_entry = (hashipv6_t *)g_list_nth_data(wdh->addrinfo_lists->ipv6_addr_list, i); + } + } + + /* set the (possibly empty) ipv6_addresses table into the main table */ + lua_setfield(L, -2, "ip6_addresses"); + + /* return the main table */ + return 1; +} + + +static int CaptureInfoConst__gc(lua_State* L _U_) { + FrameInfoConst fi = toFrameInfoConst(L,1); + if (fi) + g_free(fi); + return 0; +} + +WSLUA_ATTRIBUTES CaptureInfoConst_attributes[] = { + WSLUA_ATTRIBUTE_ROREG(CaptureInfoConst,encap), + WSLUA_ATTRIBUTE_ROREG(CaptureInfoConst,type), + WSLUA_ATTRIBUTE_ROREG(CaptureInfoConst,snapshot_length), + WSLUA_ATTRIBUTE_ROREG(CaptureInfoConst,comment), + WSLUA_ATTRIBUTE_ROREG(CaptureInfoConst,hardware), + WSLUA_ATTRIBUTE_ROREG(CaptureInfoConst,os), + WSLUA_ATTRIBUTE_ROREG(CaptureInfoConst,user_app), + WSLUA_ATTRIBUTE_ROREG(CaptureInfoConst,hosts), + { NULL, NULL, NULL } +}; + +WSLUA_META CaptureInfoConst_meta[] = { + {"__tostring", CaptureInfoConst__tostring}, + { NULL, NULL } +}; + +int CaptureInfoConst_register(lua_State* L) { + WSLUA_REGISTER_META(CaptureInfoConst); + WSLUA_REGISTER_ATTRIBUTES(CaptureInfoConst); + return 0; +} + + +WSLUA_CLASS_DEFINE(FrameInfo,FAIL_ON_NULL_OR_EXPIRED("FrameInfo"),NOP); +/* + A FrameInfo object, passed into Lua as an argument by FileHandler callback + functions (e.g., read, seek_read, etc.). + + </para><para> + This object represents frame data and + meta-data (data about the frame/packet) for a given read/seek_read/write's frame. + + </para><para> + This object's fields are written-to/set when used by read function callbacks, and + read-from/get when used by file write function callbacks. In other words, when + the Lua plugin's FileHandler read/seek_read/etc. functions are invoked, a + FrameInfo object will be passed in as one of the arguments, and its fields + should be written-to/set based on the frame information read from the file; + whereas when the Lua plugin's FileHandler write function is invoked, the + FrameInfo object passed in should have its fields read-from/get, to write that + frame information to the file. + */ + +static FrameInfo* push_FrameInfo(lua_State* L, struct wtap_pkthdr *phdr, Buffer* buf) { + FrameInfo f = (FrameInfo) g_malloc0(sizeof(struct _wslua_phdr)); + f->phdr = phdr; + f->buf = buf; + f->expired = FALSE; + return pushFrameInfo(L,f); +} + +WSLUA_METAMETHOD FrameInfo__tostring(lua_State* L) { + /* Generates a string of debug info for the FrameInfo */ + FrameInfo fi = toFrameInfo(L,1); + + if (!fi) { + lua_pushstring(L,"FrameInfo pointer is NULL!"); + } else { + if (fi->phdr) + lua_pushfstring(L, "FrameInfo: presence_flags=%d, caplen=%d, len=%d, pkt_encap=%d, opt_comment='%s'", + fi->phdr->presence_flags, fi->phdr->caplen, fi->phdr->len, fi->phdr->pkt_encap, fi->phdr->opt_comment); + else + lua_pushstring(L, "FrameInfo phdr pointer is NULL!"); + } + + WSLUA_RETURN(1); /* String of debug information. */ +} + +/* XXX: should this function be a method of File instead? */ +WSLUA_METHOD FrameInfo_read_data(lua_State* L) { + /* Tells Wireshark to read directly from given file into frame data buffer, for length bytes. Returns true if succeeded, else false. */ +#define WSLUA_ARG_FrameInfo_read_data_FILE 2 /* The File object userdata, provided by Wireshark previously in a reading-based callback */ +#define WSLUA_ARG_FrameInfo_read_data_LENGTH 3 /* The number of bytes to read from the file at the current cursor position */ + FrameInfo fi = checkFrameInfo(L,1); + File fh = checkFile(L,WSLUA_ARG_FrameInfo_read_data_FILE); + guint32 len = wslua_checkguint32(L, WSLUA_ARG_FrameInfo_read_data_LENGTH); + int err = 0; + gchar *err_info = NULL; + + if (!fi->buf || !fh->file) { + luaL_error(L, "FrameInfo read_data() got null buffer or file pointer internally"); + return 0; + } + + if (!wtap_read_packet_bytes(fh->file, fi->buf, len, &err, &err_info)) { + lua_pushboolean(L, FALSE); + if (err_info) { + lua_pushstring(L, err_info); + g_free(err_info); /* is this right? */ + } + else lua_pushnil(L); + lua_pushnumber(L, err); + return 3; + } + + lua_pushboolean(L, TRUE); + + WSLUA_RETURN(1); /* True if succeeded, else returns false along with the error number and string error description. */ +} + +/* free the struct we created, but not the phdr/buf it points to */ +static int FrameInfo__gc(lua_State* L _U_) { + FrameInfo fi = toFrameInfo(L,1); + if (fi) + g_free(fi); + return 0; +} + +/* WSLUA_ATTRIBUTE FrameInfo_time RW The packet timestamp as an NSTime object. + Note: Set the 'FileHandler.time_precision' to the appropriate 'wtap_file_tsprec' value as well. */ +static int FrameInfo_set_time (lua_State* L) { + FrameInfo fi = checkFrameInfo(L,1); + NSTime nstime = checkNSTime(L,2); + + if (!fi->phdr) return 0; + + fi->phdr->ts.secs = nstime->secs; + fi->phdr->ts.nsecs = nstime->nsecs; + + return 0; +} + +static int FrameInfo_get_time (lua_State* L) { + FrameInfo fi = checkFrameInfo(L,1); + NSTime nstime = (NSTime)g_malloc(sizeof(nstime_t)); + + if (!nstime) return 0; + + nstime->secs = fi->phdr->ts.secs; + nstime->nsecs = fi->phdr->ts.nsecs; + + pushNSTime(L,nstime); + + return 1; /* An NSTime object of the frame's timestamp. */ +} + +/* WSLUA_ATTRIBUTE FrameInfo_data RW The data buffer containing the packet. This cannot be cleared once set. */ +static int FrameInfo_set_data (lua_State* L) { + FrameInfo fi = checkFrameInfo(L,1); + + if (!fi->phdr) { + g_warning("Error in FrameInfo set data: NULL pointer"); + return 0; + } + + if (!fi->buf) { + g_warning("Error in FrameInfo set data: NULL frame_buffer pointer"); + return 0; + } + + if (lua_isstring(L,2)) { + size_t len = 0; + const gchar* s = luaL_checklstring(L,2,&len); + if (s) { + /* Make sure we have enough room for the packet */ + buffer_assure_space(fi->buf, len); + memcpy(buffer_start_ptr(fi->buf), s, len); + fi->phdr->caplen = (guint32) len; + fi->phdr->len = (guint32) len; + } else { + luaL_error(L, "FrameInfo's attribute 'data' did not get a valid Lua string"); + } + } + else + luaL_error(L, "FrameInfo's attribute 'data' must be a Lua string"); + + return 0; +} + +static int FrameInfo_get_data (lua_State* L) { + FrameInfo fi = checkFrameInfo(L,1); + + if (!fi->buf) return 0; + + lua_pushlstring(L, buffer_start_ptr(fi->buf), buffer_length(fi->buf)); + + WSLUA_RETURN(1); /* A Lua string of the frame buffer's data. */ +} + +/* WSLUA_ATTRIBUTE FrameInfo_flags RW The presence flags of the packet frame. + See 'wtap_presence_flags' in init.lua for bit values. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(FrameInfo,flags,phdr->presence_flags); +WSLUA_ATTRIBUTE_NAMED_NUMBER_SETTER(FrameInfo,flags,phdr->presence_flags,guint32); + +/* WSLUA_ATTRIBUTE FrameInfo_captured_length RW The captured packet length, + and thus the length of the buffer passed to the FrameInfo.data field. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(FrameInfo,captured_length,phdr->caplen); +WSLUA_ATTRIBUTE_NAMED_NUMBER_SETTER(FrameInfo,captured_length,phdr->caplen,guint32); + +/* WSLUA_ATTRIBUTE FrameInfo_original_length RW The on-the-wire packet length, + which may be longer than the captured_length. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(FrameInfo,original_length,phdr->len); +WSLUA_ATTRIBUTE_NAMED_NUMBER_SETTER(FrameInfo,original_length,phdr->len,guint32); + +/* WSLUA_ATTRIBUTE FrameInfo_encap RW The packet encapsulation type for the frame/packet, + if the file supports per-packet types. See 'wtap_encaps' in init.lua for possible + packet encapsulation types to use as the value for this field. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(FrameInfo,encap,phdr->pkt_encap); +WSLUA_ATTRIBUTE_NAMED_NUMBER_SETTER(FrameInfo,encap,phdr->pkt_encap,int); + +/* WSLUA_ATTRIBUTE FrameInfo_comment RW A string comment for the packet, if the + 'wtap_presence_flags.COMMENTS' was set in the presence flags; nil if there is no comment. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(FrameInfo,comment,phdr->opt_comment); +WSLUA_ATTRIBUTE_NAMED_STRING_SETTER(FrameInfo,comment,phdr->opt_comment,TRUE); + +/* This table is ultimately registered as a sub-table of the class' metatable, + * and if __index/__newindex is invoked then it calls the appropriate function + * from this table for getting/setting the members. + */ +WSLUA_ATTRIBUTES FrameInfo_attributes[] = { + WSLUA_ATTRIBUTE_RWREG(FrameInfo,flags), + WSLUA_ATTRIBUTE_RWREG(FrameInfo,captured_length), + WSLUA_ATTRIBUTE_RWREG(FrameInfo,original_length), + WSLUA_ATTRIBUTE_RWREG(FrameInfo,comment), + WSLUA_ATTRIBUTE_RWREG(FrameInfo,encap), + WSLUA_ATTRIBUTE_RWREG(FrameInfo,time), + WSLUA_ATTRIBUTE_RWREG(FrameInfo,data), + { NULL, NULL, NULL } +}; + +WSLUA_METHODS FrameInfo_methods[] = { + {"read_data", FrameInfo_read_data}, + { NULL, NULL } +}; + +WSLUA_META FrameInfo_meta[] = { + {"__tostring", FrameInfo__tostring}, + { NULL, NULL } +}; + +int FrameInfo_register(lua_State* L) { + WSLUA_REGISTER_CLASS(FrameInfo); + WSLUA_REGISTER_ATTRIBUTES(FrameInfo); + return 0; +} + +WSLUA_CLASS_DEFINE(FrameInfoConst,FAIL_ON_NULL_OR_EXPIRED("FrameInfo"),NOP); +/* + A constant FrameInfo object, passed into Lua as an argument by the FileHandler write + callback function. This has similar attributes/properties as FrameInfo, but the fields can + only be read from, not written to. + */ + +static FrameInfoConst* push_FrameInfoConst(lua_State* L, const struct wtap_pkthdr *phdr, const guint8 *pd) { + FrameInfoConst f = (FrameInfoConst) g_malloc(sizeof(struct _wslua_const_phdr)); + f->phdr = phdr; + f->pd = pd; + f->expired = FALSE; + return pushFrameInfoConst(L,f); +} + +WSLUA_METAMETHOD FrameInfoConst__tostring(lua_State* L) { + /* Generates a string of debug info for the FrameInfo */ + FrameInfoConst fi = toFrameInfoConst(L,1); + + if (!fi) { + lua_pushstring(L,"FrameInfo pointer is NULL!"); + } else { + if (fi->phdr && !fi->expired) + lua_pushfstring(L, "FrameInfo: presence_flags=%d, caplen=%d, len=%d, pkt_encap=%d, opt_comment='%s'", + fi->phdr->presence_flags, fi->phdr->caplen, fi->phdr->len, fi->phdr->pkt_encap, fi->phdr->opt_comment); + else + lua_pushfstring(L, "FrameInfo has %s", fi->phdr?"expired":"null phdr pointer"); + } + + WSLUA_RETURN(1); /* String of debug information. */ +} + +/* XXX: should this function be a method of File instead? */ +WSLUA_METHOD FrameInfoConst_write_data(lua_State* L) { + /* Tells Wireshark to write directly to given file from the frame data buffer, for length bytes. Returns true if succeeded, else false. */ +#define WSLUA_ARG_FrameInfoConst_write_data_FILE 2 /* The File object userdata, provided by Wireshark previously in a writing-based callback */ +#define WSLUA_OPTARG_FrameInfoConst_write_data_LENGTH 3 /* The number of bytes to write to the file at the current cursor position, or all if not supplied. */ + FrameInfoConst fi = checkFrameInfoConst(L,1); + File fh = checkFile(L,WSLUA_ARG_FrameInfoConst_write_data_FILE); + guint32 len = wslua_optguint32(L, WSLUA_OPTARG_FrameInfoConst_write_data_LENGTH, fi->phdr ? fi->phdr->caplen:0); + int err = 0; + + if (!fi->pd || !fi->phdr || !fh->wdh) { + luaL_error(L, "FrameInfoConst write_data() got null buffer or file pointer internally"); + return 0; + } + + if (len > fi->phdr->caplen) + len = fi->phdr->caplen; + + if (!wtap_dump_file_write(fh->wdh, fi->pd, (size_t)(len), &err)) { + lua_pushboolean(L, FALSE); + lua_pushfstring(L, "FrameInfoConst write_data() error: %s", strerror(err)); + lua_pushnumber(L, err); + return 3; + } + + lua_pushboolean(L, TRUE); + + WSLUA_RETURN(1); /* True if succeeded, else returns false along with the error number and string error description. */ +} + +/* free the struct we created, but not the wtap_pkthdr it points to */ +static int FrameInfoConst__gc(lua_State* L _U_) { + FrameInfoConst fi = toFrameInfoConst(L,1); + if (fi) + g_free(fi); + return 0; +} + +/* WSLUA_ATTRIBUTE FrameInfoConst_time RO The packet timestamp as an NSTime object. */ +static int FrameInfoConst_get_time (lua_State* L) { + FrameInfoConst fi = checkFrameInfoConst(L,1); + NSTime nstime = (NSTime)g_malloc(sizeof(nstime_t)); + + if (!nstime) return 0; + + nstime->secs = fi->phdr->ts.secs; + nstime->nsecs = fi->phdr->ts.nsecs; + + pushNSTime(L,nstime); + + return 1; /* An NSTime object of the frame's timestamp. */ +} + +/* WSLUA_ATTRIBUTE FrameInfoConst_data RO The data buffer containing the packet. */ +static int FrameInfoConst_get_data (lua_State* L) { + FrameInfoConst fi = checkFrameInfoConst(L,1); + + if (!fi->pd || !fi->phdr) return 0; + + lua_pushlstring(L, fi->pd, fi->phdr->caplen); + + return 1; +} + +/* WSLUA_ATTRIBUTE FrameInfoConst_flags RO The presence flags of the packet frame - see 'wtap_presence_flags' in init.lua for bits. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(FrameInfoConst,flags,phdr->presence_flags); + +/* WSLUA_ATTRIBUTE FrameInfoConst_captured_length RO The captured packet length, and thus the length of the buffer in the FrameInfoConst.data field. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(FrameInfoConst,captured_length,phdr->caplen); + +/* WSLUA_ATTRIBUTE FrameInfoConst_original_length RO The on-the-wire packet length, which may be longer than the captured_length. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(FrameInfoConst,original_length,phdr->len); + +/* WSLUA_ATTRIBUTE FrameInfoConst_encap RO The packet encapsulation type, if the file supports per-packet types. + See 'wtap_encaps' in init.lua for possible packet encapsulation types to use as the value for this field. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(FrameInfoConst,encap,phdr->pkt_encap); + +/* WSLUA_ATTRIBUTE FrameInfoConst_comment RO A comment for the packet; nil if there is none. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(FrameInfoConst,comment,phdr->opt_comment); + +WSLUA_ATTRIBUTES FrameInfoConst_attributes[] = { + WSLUA_ATTRIBUTE_ROREG(FrameInfoConst,flags), + WSLUA_ATTRIBUTE_ROREG(FrameInfoConst,captured_length), + WSLUA_ATTRIBUTE_ROREG(FrameInfoConst,original_length), + WSLUA_ATTRIBUTE_ROREG(FrameInfoConst,encap), + WSLUA_ATTRIBUTE_ROREG(FrameInfoConst,comment), + WSLUA_ATTRIBUTE_ROREG(FrameInfoConst,time), + WSLUA_ATTRIBUTE_ROREG(FrameInfoConst,data), + { NULL, NULL, NULL } +}; + +WSLUA_METHODS FrameInfoConst_methods[] = { + {"write_data", FrameInfoConst_write_data}, + { NULL, NULL } +}; + +WSLUA_META FrameInfoConst_meta[] = { + {"__tostring", FrameInfoConst__tostring}, + { NULL, NULL } +}; + +int FrameInfoConst_register(lua_State* L) { + WSLUA_REGISTER_CLASS(FrameInfoConst); + WSLUA_REGISTER_ATTRIBUTES(FrameInfoConst); + return 0; +} + + +WSLUA_CLASS_DEFINE(FileHandler,NOP,NOP); +/* + A FileHandler object, created by a call to FileHandler.new(arg1, arg2, ...). + The FileHandler object lets you create a file-format reader, or writer, or + both, by setting your own read_open/read or write_open/write functions. + */ + +static int filehandler_cb_error_handler(lua_State* L) { + const gchar* error = lua_tostring(L,1); + const gchar* functype = luaL_optstring(L, lua_upvalueindex(1), "UNKNOWN"); + report_failure("Lua: Error During execution of FileHandler %s callback:\n %s",functype,error); + lua_pop(L, 1); + return 0; +} + +static int push_error_handler(lua_State* L, const gchar* funcname) { + lua_pushstring(L, funcname); + lua_pushcclosure(L, filehandler_cb_error_handler, 1); + return 1; +} + + +/* During file routines, we cannot allow the FileHandler to get de-registered, since + that would change the GArray's in file_access.c and hilarity would ensue. So we + set this to true right before pcall(), and back to false afterwards */ +static gboolean in_routine = FALSE; + +/* This does the verification and setup common to all open/read/seek_read/close routines */ +#define INIT_FILEHANDLER_ROUTINE(name,retval) \ + if (!fh) { \ + g_warning("Error in file %s: no Lua FileHandler object", #name); \ + return retval; \ + } \ + if (!fh->registered) { \ + g_warning("Error in file %s: Lua FileHandler is not registered", #name); \ + return retval; \ + } \ + if (!fh->L) { \ + g_warning("Error in file %s: no FileHandler Lua state", #name); \ + return retval; \ + } \ + if (fh->name##_ref == LUA_NOREF) { \ + g_warning("Error in file %s: no FileHandler %s routine reference", #name, #name); \ + return retval; \ + } \ + L = fh->L; \ + lua_settop(L,0); \ + push_error_handler(L, #name " routine"); \ + lua_rawgeti(L, LUA_REGISTRYINDEX, fh->name##_ref); \ + if (!lua_isfunction(L, -1)) { \ + g_warning("Error in file %s: no FileHandler %s routine function in Lua", #name, #name); \ + return retval; \ + } \ + /* now guard against de-registering during pcall() */ \ + in_routine = TRUE; + +#define END_FILEHANDLER_ROUTINE() \ + /* now allow de-registering again */ \ + in_routine = TRUE; + + +/* LUA_ERRGCMM is in Lua 5.2 only - making it 9 disables it */ +#ifndef LUA_ERRGCMM +#define LUA_ERRGCMM 9 +#endif + +#define CASE_ERROR(name) \ + case LUA_ERRRUN: \ + g_warning("Run-time error while calling FileHandler %s routine", name); \ + break; \ + case LUA_ERRMEM: \ + g_warning("Memory alloc error while calling FileHandler %s routine", name); \ + break; \ + case LUA_ERRERR: \ + g_warning("Error in error handling while calling FileHandler %s routine", name); \ + break; \ + case LUA_ERRGCMM: \ + g_warning("Error in garbage collector while calling FileHandler %s routine", name); \ + break; \ + default: \ + g_assert_not_reached(); \ + break; + +#define CASE_ERROR_ERRINFO(name) \ + case LUA_ERRRUN: \ + g_warning("Run-time error while calling FileHandler %s routine", name); \ + *err_info = g_strdup_printf("Run-time error while calling FileHandler %s routine", name); \ + break; \ + case LUA_ERRMEM: \ + g_warning("Memory alloc error while calling FileHandler %s routine", name); \ + *err_info = g_strdup_printf("Memory alloc error while calling FileHandler %s routine", name); \ + break; \ + case LUA_ERRERR: \ + g_warning("Error in error handling while calling FileHandler %s routine", name); \ + *err_info = g_strdup_printf("Error in error handling while calling FileHandler %s routine", name); \ + break; \ + case LUA_ERRGCMM: \ + g_warning("Error in garbage collector while calling FileHandler %s routine", name); \ + *err_info = g_strdup_printf("Error in garbage collector while calling FileHandler %s routine", name); \ + break; \ + default: \ + g_assert_not_reached(); \ + break; + + +/* some declarations */ +static gboolean +wslua_filehandler_read(wtap *wth, int *err, gchar **err_info, + gint64 *data_offset); +static gboolean +wslua_filehandler_seek_read(wtap *wth, gint64 seek_off, + struct wtap_pkthdr *phdr, Buffer *buf, + int *err, gchar **err_info); +static void +wslua_filehandler_close(wtap *wth); +static void +wslua_filehandler_sequential_close(wtap *wth); + + +/* This is our one-and-only open routine for file handling. When called by + * file_access.c, the wtap wth argument has a void* wslua_data that holds the specific + * FileHandler for the specific registered file format reader. It has this because + * we passed it in when we registered the open routine. + * The open_file_* routines should return: + * -1 on an I/O error; + * 1 if the file they're reading is one of the types it handles; + * 0 if the file they're reading isn't the type they're checking for. + * If the routine handles this type of file, it should set the "file_type" + * field in the "struct wtap" to the type of the file. + */ +static int +wslua_filehandler_open(wtap *wth, int *err _U_, gchar **err_info) +{ + FileHandler fh = (FileHandler)(wth->wslua_data); + int retval = 0; + lua_State* L = NULL; + File *fp = NULL; + CaptureInfo *fc = NULL; + + INIT_FILEHANDLER_ROUTINE(read_open,0); + + fp = push_File(L, wth->fh); + fc = push_CaptureInfo(L, wth); + + errno = WTAP_ERR_CANT_READ; + switch ( lua_pcall(L,2,1,1) ) { + case 0: + retval = wslua_optboolint(L,-1,0); + break; + CASE_ERROR_ERRINFO("read_open") + } + + END_FILEHANDLER_ROUTINE(); + + (*fp)->expired = TRUE; + (*fc)->expired = TRUE; + + if (retval == 1) { + /* this is our file type - set the routines and settings into wtap */ + + if (fh->read_ref != LUA_NOREF) { + wth->subtype_read = wslua_filehandler_read; + } + else return 0; + + if (fh->seek_read_ref != LUA_NOREF) { + wth->subtype_seek_read = wslua_filehandler_seek_read; + } + else return 0; + + /* it's ok to not have a close routine */ + if (fh->read_close_ref != LUA_NOREF) + wth->subtype_close = wslua_filehandler_close; + else + wth->subtype_close = NULL; + + /* it's ok to not have a sequential close routine */ + if (fh->seq_read_close_ref != LUA_NOREF) + wth->subtype_sequential_close = wslua_filehandler_sequential_close; + else + wth->subtype_sequential_close = NULL; + + wth->file_type_subtype = fh->file_type; + } + + lua_settop(L,0); + return retval; +} + +/* The classic wtap read routine. This returns TRUE if it found the next packet, + * else FALSE. + * If it finds a frame/packet, it should set the pseudo-header info (ie, let Lua set it). + * Also Lua needs to set data_offset to the beginning of the line we're returning. + * This will be the seek_off parameter when this frame is re-read. +*/ +static gboolean +wslua_filehandler_read(wtap *wth, int *err, gchar **err_info, + gint64 *data_offset) +{ + FileHandler fh = (FileHandler)(wth->wslua_data); + int retval = -1; + lua_State* L = NULL; + File *fp = NULL; + FrameInfo *fi = NULL; + + INIT_FILEHANDLER_ROUTINE(read,FALSE); + + /* Reset errno */ + *err = errno = 0; + + wth->phdr.opt_comment = NULL; + + fp = push_File(L, wth->fh); + fi = push_FrameInfo(L, &wth->phdr, wth->frame_buffer); + + errno = WTAP_ERR_CANT_READ; + switch ( lua_pcall(L,2,1,1) ) { + case 0: + if (lua_isnumber(L,-1)) { + *data_offset = (gint64) lua_tonumber(L, -1); + retval = 1; + break; + } + retval = wslua_optboolint(L,-1,0); + break; + CASE_ERROR_ERRINFO("read") + } + + END_FILEHANDLER_ROUTINE(); + + (*fp)->expired = TRUE; + (*fi)->expired = TRUE; + lua_settop(L,0); + + return (retval == 1); +} + +/* Classic wtap seek_read function, called by wtap core. This must return TRUE on + * success, FALSE on error. + */ +static gboolean +wslua_filehandler_seek_read(wtap *wth, gint64 seek_off, + struct wtap_pkthdr *phdr, Buffer *buf, + int *err, gchar **err_info) +{ + FileHandler fh = (FileHandler)(wth->wslua_data); + int retval = -1; + lua_State* L = NULL; + File *fp = NULL; + FrameInfo *fi = NULL; + + INIT_FILEHANDLER_ROUTINE(seek_read,FALSE); + + /* Reset errno */ + *err = errno = 0; + phdr->opt_comment = NULL; + + fp = push_File(L, wth->random_fh); + fi = push_FrameInfo(L, phdr, buf); + lua_pushnumber(L, (lua_Number)seek_off); + + *err = WTAP_ERR_CANT_READ; + switch ( lua_pcall(L,3,1,1) ) { + case 0: + if (lua_isstring(L,-1)) { + size_t len = 0; + const gchar* fd = lua_tolstring(L, -1, &len); + if (len < WTAP_MAX_PACKET_SIZE) + memcpy(buffer_start_ptr(buf), fd, len); + retval = 1; + break; + } + retval = wslua_optboolint(L,-1,0); + break; + CASE_ERROR_ERRINFO("seek_read") + } + + END_FILEHANDLER_ROUTINE(); + + (*fp)->expired = TRUE; + (*fi)->expired = TRUE; + lua_settop(L,0); + + return (retval == 1); +} + +/* Classic wtap close function, called by wtap core. + */ +static void +wslua_filehandler_close(wtap *wth) +{ + FileHandler fh = (FileHandler)(wth->wslua_data); + lua_State* L = NULL; + File *fp = NULL; + + INIT_FILEHANDLER_ROUTINE(read_close,); + + fp = push_File(L, wth->fh); + + switch ( lua_pcall(L,1,1,1) ) { + case 0: + break; + CASE_ERROR("read_close") + } + + END_FILEHANDLER_ROUTINE(); + + (*fp)->expired = TRUE; + lua_settop(L,0); + + return; +} + +/* Classic wtap sequential close function, called by wtap core. + */ +static void +wslua_filehandler_sequential_close(wtap *wth) +{ + FileHandler fh = (FileHandler)(wth->wslua_data); + lua_State* L = NULL; + File *fp = NULL; + + INIT_FILEHANDLER_ROUTINE(seq_read_close,); + + fp = push_File(L, wth->fh); + + switch ( lua_pcall(L,1,1,1) ) { + case 0: + break; + CASE_ERROR("seq_read_close") + } + + END_FILEHANDLER_ROUTINE(); + + (*fp)->expired = TRUE; + lua_settop(L,0); + + return; +} + + +/* basically a dummy function to use for can_write_encap so that the caller calls + * wslua_can_write_encap instead (which will be wslua_filehandler_can_write_encap) + */ +static int +wslua_dummy_can_write_encap(int encap _U_) +{ + return WTAP_ERR_CHECK_WSLUA; +} + +/* Similar to the classic wtap can_write_encap function. + * This returns 0 if the encap is ok for this file type. + */ +static int +wslua_filehandler_can_write_encap(int encap, void* data) +{ + FileHandler fh = (FileHandler)(data); + int retval = WTAP_ERR_UNSUPPORTED_ENCAP; + lua_State* L = NULL; + + INIT_FILEHANDLER_ROUTINE(can_write_encap,WTAP_ERR_INTERNAL); + + lua_pushnumber(L, encap); + + errno = WTAP_ERR_CANT_READ; + switch ( lua_pcall(L,1,1,1) ) { + case 0: + retval = wslua_optboolint(L,-1,WTAP_ERR_UNSUPPORTED_ENCAP); + break; + CASE_ERROR("can_write_encap") + } + + END_FILEHANDLER_ROUTINE(); + + /* the retval we got was either a 1 for true, 0 for false, or WTAP_ERR_UNSUPPORTED_ENCAP; + but can_write_encap() expects 0 to be true/yes */ + if (retval == 1) { + retval = 0; + } else if (retval == 0) { + retval = WTAP_ERR_UNSUPPORTED_ENCAP; + } + + return retval; +} + +/* some declarations */ +static gboolean +wslua_filehandler_dump(wtap_dumper *wdh, const struct wtap_pkthdr *phdr, + const guint8 *pd, int *err); +static gboolean +wslua_filehandler_dump_close(wtap_dumper *wdh, int *err); + + +/* The classic wtap dump_open function. + * This returns 1 (TRUE) on success. + */ +static int +wslua_filehandler_dump_open(wtap_dumper *wdh, int *err) +{ + FileHandler fh = (FileHandler)(wdh->wslua_data); + int retval = 0; + lua_State* L = NULL; + File *fp = NULL; + CaptureInfoConst *fc = NULL; + + INIT_FILEHANDLER_ROUTINE(write_open,0); + + fp = push_Wdh(L, wdh); + fc = push_CaptureInfoConst(L,wdh); + + /* Reset err */ + *err = 0; + + switch ( lua_pcall(L,2,1,1) ) { + case 0: + retval = wslua_optboolint(L,-1,0); + break; + CASE_ERROR("write_open") + } + + END_FILEHANDLER_ROUTINE(); + + (*fp)->expired = TRUE; + (*fc)->expired = TRUE; + + if (retval == 1) { + /* this is our file type - set the routines and settings into wtap */ + + if (fh->write_ref != LUA_NOREF) { + wdh->subtype_write = wslua_filehandler_dump; + } + else { + g_warning("FileHandler was not set with a write function, even though write_open() returned true"); + return 0; + } + + /* it's ok to not have a close routine */ + if (fh->write_close_ref != LUA_NOREF) + wdh->subtype_close = wslua_filehandler_dump_close; + else + wdh->subtype_close = NULL; + } + + return retval; +} + +/* The classic wtap dump routine. This returns TRUE if it writes the current packet, + * else FALSE. +*/ +static gboolean +wslua_filehandler_dump(wtap_dumper *wdh, const struct wtap_pkthdr *phdr, + const guint8 *pd, int *err) +{ + FileHandler fh = (FileHandler)(wdh->wslua_data); + int retval = -1; + lua_State* L = NULL; + File *fp = NULL; + FrameInfoConst *fi = NULL; + + INIT_FILEHANDLER_ROUTINE(write,FALSE); + + /* Reset errno */ + *err = errno = 0; + + fp = push_Wdh(L, wdh); + fi = push_FrameInfoConst(L, phdr, pd); + + errno = WTAP_ERR_CANT_READ; + switch ( lua_pcall(L,2,1,1) ) { + case 0: + retval = wslua_optboolint(L,-1,0); + break; + CASE_ERROR("write") + } + + END_FILEHANDLER_ROUTINE(); + + (*fp)->expired = TRUE; + (*fi)->expired = TRUE; + + return (retval == 1); +} + +/* The classic wtap dump_close routine. This returns TRUE if it closes cleanly, + * else FALSE. +*/ +static gboolean +wslua_filehandler_dump_close(wtap_dumper *wdh, int *err) +{ + FileHandler fh = (FileHandler)(wdh->wslua_data); + int retval = -1; + lua_State* L = NULL; + File *fp = NULL; + + INIT_FILEHANDLER_ROUTINE(write_close,FALSE); + + /* Reset errno */ + *err = errno = 0; + + fp = push_Wdh(L, wdh); + + errno = WTAP_ERR_CANT_READ; + switch ( lua_pcall(L,1,1,1) ) { + case 0: + retval = wslua_optboolint(L,-1,0); + break; + CASE_ERROR("write_close") + } + + END_FILEHANDLER_ROUTINE(); + + (*fp)->expired = TRUE; + + return (retval == 1); +} + + +WSLUA_CONSTRUCTOR FileHandler_new(lua_State* L) { + /* Creates a new FileHandler */ +#define WSLUA_ARG_FileHandler_new_NAME 1 /* The name of the file type, for display purposes only. E.g., "Wireshark - pcapng" */ +#define WSLUA_ARG_FileHandler_new_SHORTNAME 2 /* the file type short name, used as a shortcut in various places. E.g., "pcapng". Note: the name cannot already be in use. */ +#define WSLUA_ARG_FileHandler_new_DESCRIPTION 3 /* Descriptive text about this file format, for display purposes only */ +#define WSLUA_ARG_FileHandler_new_TYPE 4 /* The type of FileHandler, "r"/"w"/"rw" for reader/writer/both, include "m" for magic, "s" for strong heuristic */ + + const gchar* name = luaL_checkstring(L,WSLUA_ARG_FileHandler_new_NAME); + const gchar* short_name = luaL_checkstring(L,WSLUA_ARG_FileHandler_new_SHORTNAME); + const gchar* desc = luaL_checkstring(L,WSLUA_ARG_FileHandler_new_DESCRIPTION); + const gchar* type = luaL_checkstring(L,WSLUA_ARG_FileHandler_new_TYPE); + FileHandler fh = (FileHandler) g_malloc0(sizeof(struct _wslua_filehandler)); + + fh->is_reader = (strchr(type,'r') != NULL) ? TRUE : FALSE; + fh->is_writer = (strchr(type,'w') != NULL) ? TRUE : FALSE; + + if (fh->is_reader && wtap_has_open_info(short_name)) { + return luaL_error(L, "FileHandler.new: '%s' short name already exists for a reader!", short_name); + } + + if (fh->is_writer && wtap_short_string_to_file_type_subtype(short_name) > -1) { + return luaL_error(L, "FileHandler.new: '%s' short name already exists for a writer!", short_name); + } + + fh->type = g_strdup(type); + fh->finfo.name = g_strdup(name); + fh->finfo.short_name = g_strdup(short_name); + fh->finfo.default_file_extension = NULL; + fh->finfo.additional_file_extensions = NULL; + fh->finfo.writing_must_seek = FALSE; + fh->finfo.has_name_resolution = FALSE; + fh->finfo.can_write_encap = NULL; + fh->finfo.dump_open = NULL; + /* this will be set to a new file_type when registered */ + fh->file_type = WTAP_FILE_TYPE_SUBTYPE_UNKNOWN; + + fh->description = g_strdup(desc); + fh->L = L; + fh->read_open_ref = LUA_NOREF; + fh->read_ref = LUA_NOREF; + fh->seek_read_ref = LUA_NOREF; + fh->read_close_ref = LUA_NOREF; + fh->seq_read_close_ref = LUA_NOREF; + fh->write_open_ref = LUA_NOREF; + fh->write_ref = LUA_NOREF; + fh->write_close_ref = LUA_NOREF; + fh->can_write_encap_ref = LUA_NOREF; + + fh->registered = FALSE; + + pushFileHandler(L,fh); + WSLUA_RETURN(1); /* The newly created FileHandler object */ +} + +WSLUA_METAMETHOD FileHandler__tostring(lua_State* L) { + /* Generates a string of debug info for the FileHandler */ + FileHandler fh = toFileHandler(L,1); + + if (!fh) { + lua_pushstring(L,"FileHandler pointer is NULL!"); + } else { + lua_pushfstring(L, "FileHandler(%s): short-name='%s', description='%s', read_open=%d, read=%d, write=%d", + fh->finfo.name, fh->finfo.short_name, fh->description, fh->read_open_ref, fh->read_ref, fh->write_ref); + } + + WSLUA_RETURN(1); /* String of debug information. */ +} + +static int FileHandler__gc(lua_State* L _U_) { + /* do NOT free FileHandler, it's never free'd */ + /* TODO: handle this and other Lua things that should be free'd on exit, in a better way */ + return 0; +} + +/* A Lua File handler must not be expired, and must be either a reader or writer, and + * a *reader* one MUST at least define read_open, read, and seek_read funcs; and + * a *writer* one MUST at least define can_write_encap, write_open, and write funcs + */ +static gboolean verify_filehandler_complete(FileHandler fh) { + return ((fh->is_reader || fh->is_writer) && + (!fh->is_reader || + (fh->is_reader && + fh->read_open_ref != LUA_NOREF && + fh->read_ref != LUA_NOREF && + fh->seek_read_ref != LUA_NOREF)) && + (!fh->is_writer || + (fh->is_writer && + fh->can_write_encap_ref != LUA_NOREF && + fh->write_open_ref != LUA_NOREF && + fh->write_ref != LUA_NOREF)) ); +} + + +WSLUA_FUNCTION wslua_register_filehandler(lua_State* L) { + /* Register the FileHandler into Wireshark/tshark, so they can read/write this new format. + All functions and settings must be complete before calling this registration function. + This function cannot be called inside the reading/writing callback functions. */ +#define WSLUA_ARG_register_filehandler_FILEHANDLER 1 /* the FileHandler object to be registered */ + FileHandler fh = checkFileHandler(L,WSLUA_ARG_register_filehandler_FILEHANDLER); + + if (in_routine) + return luaL_error(L,"a FileHAndler cannot be registered during reading/writing callback functions"); + + if (fh->registered) + return luaL_error(L,"this FileHandler is already registered"); + + if (!verify_filehandler_complete(fh)) + return luaL_error(L,"this FileHandler is not complete enough to register"); + + if (fh->is_writer) { + fh->finfo.can_write_encap = wslua_dummy_can_write_encap; + fh->finfo.wslua_info = (wtap_wslua_file_info_t*) g_malloc0(sizeof(wtap_wslua_file_info_t)); + fh->finfo.wslua_info->wslua_can_write_encap = wslua_filehandler_can_write_encap; + fh->finfo.wslua_info->wslua_data = (void*)(fh); + fh->finfo.dump_open = wslua_filehandler_dump_open; + } + + fh->file_type = wtap_register_file_type_subtypes(&(fh->finfo),fh->file_type); + + if (fh->is_reader) { + struct open_info oi = { NULL, OPEN_INFO_HEURISTIC, NULL, NULL, NULL }; + oi.name = fh->finfo.short_name; + oi.open_routine = wslua_filehandler_open; + oi.extensions = fh->finfo.additional_file_extensions; + oi.wslua_data = (void*)(fh); + if (strchr(fh->type,'m') != NULL) { + oi.type = OPEN_INFO_MAGIC; + } else { + oi.type = OPEN_INFO_HEURISTIC; + } + wtap_register_open_info(&oi, (strchr(fh->type,'s') != NULL)); + } + + fh->registered = TRUE; + + lua_pushnumber(L, fh->file_type); + + WSLUA_RETURN(1); /* the new type number for this file reader/write */ +} + +WSLUA_FUNCTION wslua_deregister_filehandler(lua_State* L) { + /* De-register the FileHandler from Wireshark/tshark, so it no longer gets used for reading/writing/display. + This function cannot be called inside the reading/writing callback functions. */ +#define WSLUA_ARG_register_filehandler_FILEHANDLER 1 /* the FileHandler object to be de-registered */ + FileHandler fh = checkFileHandler(L,WSLUA_ARG_register_filehandler_FILEHANDLER); + + if (in_routine) + return luaL_error(L,"A FileHAndler cannot be de-registered during reading/writing callback functions"); + + if (!fh->registered) + return 0; + + /* undo writing stuff, even if it wasn't a writer */ + fh->finfo.can_write_encap = NULL; + if (fh->finfo.wslua_info) { + fh->finfo.wslua_info->wslua_can_write_encap = NULL; + fh->finfo.wslua_info->wslua_data = NULL; + g_free(fh->finfo.wslua_info); + fh->finfo.wslua_info = NULL; + } + fh->finfo.dump_open = NULL; + + if (fh->file_type != WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) + wtap_deregister_file_type_subtype(fh->file_type); + + if (fh->is_reader && wtap_has_open_info(fh->finfo.short_name)) { + wtap_deregister_open_info(fh->finfo.short_name); + } + + fh->registered = FALSE; + + return 0; +} + +/* The folllowing macros generate setter functions for Lua, for the following Lua + function references in _wslua_filehandler struct: + int read_open_ref; + int read_ref; + int seek_read_ref; + int read_close_ref; + int seq_read_close_ref; + int can_write_encap_ref; + int write_open_ref; + int write_ref; + int write_close_ref; +*/ + +/* WSLUA_ATTRIBUTE FileHandler_read_open WO The Lua function to be called when Wireshark opens a file for reading. + + </para><para> + When later called by Wireshark, the Lua function will be given (1) a File object, and (2) a CaptureInfo object. + + </para><para> + The purpose of the Lua function set to this 'read_open' field is to check if the file Wireshark is opening is of its type, + for example by checking for magic numbers or trying to parse records in the file, etc. The more can be verified + the better, because Wireshark tries all file readers until it finds one that accepts the file, so accepting an + incorrect file prevents other file readers from reading their files. + + </para><para> + The called Lua function should return true if the file is its type (it accepts it), false if not. The Lua + function must also set the File offset position (using file:seek()) to where it wants it to be for its first + read() call, or back to beginning if it returns false. + */ +WSLUA_ATTRIBUTE_FUNC_SETTER(FileHandler,read_open); + +/* WSLUA_ATTRIBUTE FileHandler_read WO The Lua function to be called when Wireshark wants to read a packet from the file. + + </para><para> + When later called by Wireshark, the Lua function will be given (1) a File object, and (2) a FrameInfo object. + + </para><para> + The purpose of the Lua function set to this 'read' field is to read the next packet from the file, and setting the parsed/read + packet into the frame buffer using 'FrameInfo.data = foo' or 'FrameInfo:read_data()'. + + </para><para> + The called Lua function should return the file offset/position number where the packet begins, or false if it hit an + error. The file offset will be saved by Wireshark and passed into the set seek_read() Lua function later. */ +WSLUA_ATTRIBUTE_FUNC_SETTER(FileHandler,read); + +/* WSLUA_ATTRIBUTE FileHandler_seek_read WO The Lua function to be called when Wireshark wants to read a packet from the file at the given offset. + + </para><para> + When later called by Wireshark, the Lua function will be given (1) a File object, and (2) a FrameInfo object, + and (3) the file offset number previously set by the read function call. */ +WSLUA_ATTRIBUTE_FUNC_SETTER(FileHandler,seek_read); + +/* WSLUA_ATTRIBUTE FileHandler_read_close WO The Lua function to be called when Wireshark wants to close the read file completely. + + </para><para> + When later called by Wireshark, the Lua function will be given (1) a File object. + + </para><para> + It is not necessary to set this field to a Lua function - FileHandler can be registered without doing so - it + is available in case there is memory/state to clear in your script when the file is closed. */ +WSLUA_ATTRIBUTE_FUNC_SETTER(FileHandler,read_close); + +/* WSLUA_ATTRIBUTE FileHandler_seq_read_close WO The Lua function to be called when Wireshark wants to close the sequentially-read file. + + </para><para> + When later called by Wireshark, the Lua function will be given a File object. + + </para><para> + It is not necessary to set this field to a Lua + function - FileHandler can be registered without doing so - it is available in case there is memory/state to clear in your script + when the file is closed for the sequential reading portion. After this point, there will be no more calls to read(), only seek_read(). */ +WSLUA_ATTRIBUTE_FUNC_SETTER(FileHandler,seq_read_close); + + +/* WSLUA_ATTRIBUTE FileHandler_can_write_encap WO The Lua function to be called when Wireshark wants to write a file, + by checking if this file writer can handle the wtap packet encapsulation(s). + + </para><para> + When later called by Wireshark, the Lua function will be given a Lua number, which matches one of the encapsulations + in the Lua 'wtap_encaps' table. This might be the 'wtap_encap.PER_PACKET' number, meaning the capture contains multiple + encapsulation types, and the file reader should only return true if it can handle multiple encap types in one file. The + function will then be called again, once for each encap type in the file, to make sure it can write each one. + + </para><para> + If the Lua file writer can write the given type of encapsulation into a file, then it returns the boolean true, else false. */ +WSLUA_ATTRIBUTE_FUNC_SETTER(FileHandler,can_write_encap); + +/* WSLUA_ATTRIBUTE FileHandler_write_open WO The Lua function to be called when Wireshark opens a file for writing. + + </para><para> + When later called by Wireshark, the Lua function will be given (1) a File object, and (2) a CaptureInfoConst object. + + </para><para> + The purpose of the Lua function set to this 'write_open' field is similar to the read_open callback function: + to initialize things necessary for writing the capture to a file. For example, if the output file format has a + file header, then the file header should be written within this write_open function. + + </para><para> + The called Lua function should return true on success, or false if it hit an error. + Also make sure to set the FileHandler.write (and potentially FileHandler.write_close) functions before + returning true from this function. */ +WSLUA_ATTRIBUTE_FUNC_SETTER(FileHandler,write_open); + +/* WSLUA_ATTRIBUTE FileHandler_write WO The Lua function to be called when Wireshark wants to write a packet to the file. + + </para><para> + When later called by Wireshark, the Lua function will be given (1) a File object, and (2) a FrameInfoConst object + of the current frame/packet to be written. + + </para><para> + The purpose of the Lua function set to this 'write' field is to write the next packet to the file. + + </para><para> + The called Lua function should return true on succcess, or false if it hit an error. */ +WSLUA_ATTRIBUTE_FUNC_SETTER(FileHandler,write); + +/* WSLUA_ATTRIBUTE FileHandler_write_close WO The Lua function to be called when Wireshark wants to close the written file. + + </para><para> + When later called by Wireshark, the Lua function will be given a File object. + + </para><para> + It is not necessary to set this field to a Lua function - FileHandler can be registered without doing so - it is available + in case there is memory/state to clear in your script when the file is closed. */ +WSLUA_ATTRIBUTE_FUNC_SETTER(FileHandler,write_close); + +/* generate other member accessors setters/getters */ + +/* WSLUA_ATTRIBUTE FileHandler_type RO The internal file type. This is automatically set with a new + number when the FileHandler is registered. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(FileHandler,type,file_type); + +/* WSLUA_ATTRIBUTE FileHandler_extensions RW One or more file extensions that this file type usually uses. + + </para><para> + For readers using heuristics to determine file type, Wireshark will try the readers of the file's + extension first, before trying other readers. But ultimately Wireshark tries all file readers + for any file extension, until it finds one that accepts the file. */ +WSLUA_ATTRIBUTE_NAMED_STRING_GETTER(FileHandler,extensions,finfo.additional_file_extensions); +WSLUA_ATTRIBUTE_NAMED_STRING_SETTER(FileHandler,extensions,finfo.additional_file_extensions,TRUE); + +/* WSLUA_ATTRIBUTE FileHandler_writing_must_seek RW true if the ability to seek is required when writing + this file format, else false. This will be checked by Wireshark when writing out to compressed + file formats, because seeking is not possible with compressed files. Usually a file writer only + needs to be able to seek if it needs to go back in the file to change something, such as a block or + file length value earlier in the file. */ +WSLUA_ATTRIBUTE_NAMED_BOOLEAN_GETTER(FileHandler,writing_must_seek,finfo.writing_must_seek); +WSLUA_ATTRIBUTE_NAMED_BOOLEAN_SETTER(FileHandler,writing_must_seek,finfo.writing_must_seek); + +/* WSLUA_ATTRIBUTE FileHandler_writes_name_resolution RW true if the file format supports name resolution + records, else false. */ +WSLUA_ATTRIBUTE_NAMED_BOOLEAN_GETTER(FileHandler,writes_name_resolution,finfo.has_name_resolution); +WSLUA_ATTRIBUTE_NAMED_BOOLEAN_SETTER(FileHandler,writes_name_resolution,finfo.has_name_resolution); + +/* WSLUA_ATTRIBUTE FileHandler_supported_comment_types RW set to the bit-wise OR'ed number representing + the type of comments the file writer supports writing, based on the numbers in the 'wtap_comments' table. */ +WSLUA_ATTRIBUTE_NAMED_NUMBER_GETTER(FileHandler,supported_comment_types,finfo.supported_comment_types); +WSLUA_ATTRIBUTE_NAMED_NUMBER_SETTER(FileHandler,supported_comment_types,finfo.supported_comment_types,guint32); + +/* This table is ultimately registered as a sub-table of the class' metatable, + * and if __index/__newindex is invoked then it calls the appropriate function + * from this table for getting/setting the members. + */ +WSLUA_ATTRIBUTES FileHandler_attributes[] = { + WSLUA_ATTRIBUTE_WOREG(FileHandler,read_open), + WSLUA_ATTRIBUTE_WOREG(FileHandler,read), + WSLUA_ATTRIBUTE_WOREG(FileHandler,seek_read), + WSLUA_ATTRIBUTE_WOREG(FileHandler,read_close), + WSLUA_ATTRIBUTE_WOREG(FileHandler,seq_read_close), + WSLUA_ATTRIBUTE_WOREG(FileHandler,can_write_encap), + WSLUA_ATTRIBUTE_WOREG(FileHandler,write_open), + WSLUA_ATTRIBUTE_WOREG(FileHandler,write), + WSLUA_ATTRIBUTE_WOREG(FileHandler,write_close), + WSLUA_ATTRIBUTE_ROREG(FileHandler,type), + WSLUA_ATTRIBUTE_RWREG(FileHandler,extensions), + WSLUA_ATTRIBUTE_RWREG(FileHandler,writing_must_seek), + WSLUA_ATTRIBUTE_RWREG(FileHandler,writes_name_resolution), + WSLUA_ATTRIBUTE_RWREG(FileHandler,supported_comment_types), + { NULL, NULL, NULL } +}; + +WSLUA_METHODS FileHandler_methods[] = { + {"new", FileHandler_new}, + { NULL, NULL } +}; + +WSLUA_META FileHandler_meta[] = { + {"__tostring", FileHandler__tostring}, + { NULL, NULL } +}; + +int FileHandler_register(lua_State* L) { + WSLUA_REGISTER_CLASS(FileHandler); + WSLUA_REGISTER_ATTRIBUTES(FileHandler); + return 0; +} diff --git a/epan/wslua/wslua_util.c b/epan/wslua/wslua_util.c index d912607fef..f66310bf7c 100644 --- a/epan/wslua/wslua_util.c +++ b/epan/wslua/wslua_util.c @@ -287,7 +287,7 @@ WSLUA_CONSTRUCTOR Dir_open(lua_State* L) { } dir = (Dir)g_malloc(sizeof(struct _wslua_dir)); - dir->dir = OPENDIR_OP(dirname_clean); + dir->dir = g_dir_open(dirname_clean, 0, dir->dummy); g_free(dirname_clean); dir->ext = extension ? g_strdup(extension) : NULL; dir->dummy = (GError **)g_malloc(sizeof(GError *)); @@ -309,7 +309,7 @@ WSLUA_METAMETHOD Dir__call(lua_State* L) { /* At every invocation will return one file (nil when done) */ Dir dir = checkDir(L,1); - const FILE_T* file; + const gchar* file; const gchar* filename; const char* ext; @@ -317,21 +317,20 @@ WSLUA_METAMETHOD Dir__call(lua_State* L) { return 0; } - if ( ! ( file = DIRGETNEXT_OP(dir->dir ) )) { - CLOSEDIR_OP(dir->dir); + if ( ! ( file = g_dir_read_name(dir->dir ) )) { + g_dir_close(dir->dir); dir->dir = NULL; return 0; } if ( ! dir->ext ) { - filename = GETFNAME_OP(file); - lua_pushstring(L,filename); + lua_pushstring(L,file); return 1; } do { - filename = GETFNAME_OP(file); + filename = file; /* XXX strstr returns ptr to first match, this fails ext=".xxx" filename="aaa.xxxz.xxx" */ @@ -339,9 +338,9 @@ WSLUA_METAMETHOD Dir__call(lua_State* L) { lua_pushstring(L,filename); return 1; } - } while(( file = DIRGETNEXT_OP(dir->dir) )); + } while(( file = g_dir_read_name(dir->dir) )); - CLOSEDIR_OP(dir->dir); + g_dir_close(dir->dir); dir->dir = NULL; return 0; } @@ -351,7 +350,7 @@ WSLUA_METHOD Dir_close(lua_State* L) { Dir dir = checkDir(L,1); if (dir->dir) { - CLOSEDIR_OP(dir->dir); + g_dir_close(dir->dir); dir->dir = NULL; } @@ -365,7 +364,7 @@ static int Dir__gc(lua_State* L) { if(!dir) return 0; if (dir->dir) { - CLOSEDIR_OP(dir->dir); + g_dir_close(dir->dir); } g_free(dir->dummy); diff --git a/test/captures/sip.pcapng b/test/captures/sip.pcapng Binary files differnew file mode 100644 index 0000000000..58373b9aa5 --- /dev/null +++ b/test/captures/sip.pcapng diff --git a/test/captures/sipmsg.log b/test/captures/sipmsg.log new file mode 100644 index 0000000000..d69c99c072 --- /dev/null +++ b/test/captures/sipmsg.log @@ -0,0 +1,136 @@ +File opened. +Mar 6 13:34:22.599 UDP[3:0]10.102.131.194:5060 OPENED +Mar 6 13:34:22.616 UDP[6:0]10.102.130.185:5060 OPENED +Mar 6 13:34:49.416 On [6:0]10.102.130.185:5060 received from 10.102.130.150:5060 +REGISTER sip:csp.noklab.net SIP/2.0 +Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK26b7a48d +From: sip:34903@csp.noklab.net +To: sip:34903@csp.noklab.net +Call-ID: 003094c3-a0160002-23aa7e86-29e5808d@192.168.1.100 +CSeq: 144 REGISTER +User-Agent: CSCO/7 +Contact: <sip:34903@192.168.1.100:5060> +Content-Length: 0 +Expires: 3600 + + +---------------------------------------- +Mar 6 13:34:49.516 On [6:0]10.102.130.185:5060 sent to 10.102.130.150:5060 +SIP/2.0 200 OK +Via: SIP/2.0/UDP 192.168.1.100:5060;received=10.102.130.150;branch=z9hG4bK26b7a48d;rport=5060 +From: sip:34903@csp.noklab.net +To: sip:34903@csp.noklab.net +Call-ID: 003094c3-a0160002-23aa7e86-29e5808d@192.168.1.100 +CSeq: 144 REGISTER +Contact: <sip:34903@192.168.1.100:5060>;expires=34 + + +---------------------------------------- +Mar 6 13:39:06.100 On 127.0.0.1:5060 received from 127.0.0.1:5070 +INVITE sip:17324201111@135.25.31.10:5060;acme_realm=cpea8500 SIP/2.0 +Via: SIP/2.0/UDP 127.0.0.1:5070;branch=z9hG4bKIWFuqpq6n00c0o1eckfm741;acme_irealm=public;acme_sa=192.168.109.112 +Contact: "B5-2C23-052 Blu"<sip:7323685154@127.0.0.1:5070> +GenericID: 117318834600008@0008250123d0 +Supported: 100rel +From: "B5-2C23-052 Blu"<sip:7323685154@127.25.29.135:5060>;tag=0000047b000ce0e0 +To: <sip:17324201111@135.25.31.10:5060> +Call-ID: 7f00000113ce0000047b000cd140@127.0.0.1 +CSeq: 2 INVITE +P-Asserted-Identity: "B5-2C23-052 Blu"<sip:7323685154@127.25.29.135:5060> +Content-Length: 187 +Content-Type: application/sdp + +v=0 +o=IWF 10 10 IN IP4 192.168.109.113 +s=H323 Call +c=IN IP4 192.168.109.113 +t=0 0 +m=audio 29156 RTP/AVP 18 0 +a=rtpmap:18 G729/8000/1 +a=fmtp:18 annexb=yes +a=rtpmap:0 PCMU/8000/1 + +---------------------------------------- +Mar 6 13:39:06.104 On 127.0.0.1:5060 sent to 127.0.0.1:5070 +SIP/2.0 100 Trying +Via: SIP/2.0/UDP 127.0.0.1:5070;branch=z9hG4bKIWFuqpq6n00c0o1eckfm741 +From: "B5-2C23-052 Blu"<sip:7323685154@127.0.0.1:5060>;tag=0000047b000ce0e0 +To: <sip:17324201111@127.0.0.1:5060> +Call-ID: 7f00000113ce0000047b000cd140@127.0.0.1 +CSeq: 2 INVITE + + +---------------------------------------- +Mar 6 13:39:06.122 On 127.0.0.1:2945 sent to 127.0.0.1:2944 + 0000: ac 3e fd 01 00 07 89 d9 00 fc 10 00 00 00 02 00 .>.............. + 0010: 00 00 00 00 f1 21 00 00 00 02 00 6d 30 00 6a 8c .....!.....m0.j. + 0020: 00 02 20 01 80 00 06 70 75 62 6c 69 63 83 00 05 .. ....public... + 0030: 24 57 45 53 54 84 00 08 63 70 65 61 38 35 30 30 $WEST...cpea8500 + 0040: 86 00 05 24 45 41 53 54 88 00 01 01 8b 00 01 00 ...$EAST........ + 0050: 89 00 02 00 02 8a 00 04 00 00 00 00 98 00 04 00 ................ + 0060: 00 00 00 99 00 04 00 00 00 00 9a 00 04 00 00 00 ................ + 0070: 00 94 00 04 87 19 1f 0a 96 00 01 01 a7 00 01 00 ................ + 0080: a8 00 01 00 a9 00 02 00 00 21 00 00 00 02 00 76 .........!.....v + 0090: 30 00 73 8c 00 02 10 01 80 00 08 63 70 65 61 38 0.s........cpea8 + 00a0: 35 30 30 83 00 05 24 45 41 53 54 84 00 06 70 75 500...$EAST...pu + 00b0: 62 6c 69 63 86 00 05 24 57 45 53 54 87 00 06 c0 blic...$WEST.... + 00c0: a8 6d 71 71 e4 88 00 01 01 8b 00 01 01 89 00 02 .mqq............ + 00d0: 00 02 8a 00 04 00 00 00 00 98 00 04 00 00 00 00 ................ + 00e0: 99 00 04 00 00 00 00 9a 00 04 00 00 00 00 94 00 ................ + 00f0: 04 7f 00 00 01 96 00 01 01 a7 00 01 00 a8 00 01 ................ + 0100: 00 a9 00 02 00 00 ...... +Transaction = 494041 { + Context = $ { + Add = $ { + Flow { + index=1E + irealm=public + idest=$WEST + erealm=cpea8500 + esource=$EAST + media=audio + trans=UDP + mode=off + num=2 + bw=0 + peakr=0 + avgr=0 + mbs=0 + subscr=135.25.31.10 + } + }, + Add = $ { + Flow { + index=1W + irealm=cpea8500 + idest=$EAST + erealm=public + esource=$WEST + edest=192.168.109.113:29156 + media=audio + trans=UDP + mode=1way + num=2 + bw=0 + peakr=0 + avgr=0 + mbs=0 + subscr=127.0.0.1 + } + } + } +} +---------------------------------------- +Mar 6 13:39:06.127 On 127.0.0.1:5060 sent to 127.0.0.1:5070 +SIP/2.0 181 Call Is Being Forwarded +Via: SIP/2.0/UDP 127.0.0.1:5070;branch=z9hG4bKIWFuqpq6n00c0o1eckfm741;acme_iwf_2833_preferred=101 +From: "B5-2C23-052 Blu"<sip:7323685154@127.25.29.135:5060>;tag=0000047b000ce0e0 +To: <sip:17324201111@135.25.31.10:5060> +Call-ID: 7f00000113ce0000047b000cd140@127.0.0.1 +CSeq: 2 INVITE + + +---------------------------------------- +Jun 8 14:35:50.233 UDP[3:0]10.102.131.194:5060 CLOSED +Jun 8 14:35:50.233 UDP[6:0]10.102.130.185:5060 CLOSED +File closed. diff --git a/test/lua/acme_file.lua b/test/lua/acme_file.lua new file mode 100644 index 0000000000..8eeaea5473 --- /dev/null +++ b/test/lua/acme_file.lua @@ -0,0 +1,1365 @@ +------------------------------------------ +-- acme_file_reader.lua +-- Author: Hadriel Kaplan (hadrielk at yahoo dot com) +-- version = 1.0 +-- date = 3/3/2014 +------------------------------------------ +--[[ + This is a Wireshark Lua-based capture file reader. + This "capture file" reader reads message logs from Acme Packet (now Oracle) Session Border Controllers, + such as sipmsg.log files. There are several variants of the log file format, as well as some changes that + can happen based on how the log file is generated and retrieved; for example if it's generated through a + 'tail' command, or FTP'ed by a FTP client which adds carriage-returns. This Lua file reader tries to handle + such conditions. + + Note: this script wasn't written to be super-efficient, nor clever. When you've been writing Lua for a while + you get used to writing in a different, more elegant fashion than this script is; but other people find it + hard to read such Lua code, so I've tried to keep this simpler. + + Features: + -handles sipmsg type logs, sipdns type logs, algd type logs + -handles both IPv4 and IPv6, for both UDP and TCP + -reads sipmsg logs from 3800, 4250, 4500, 9200, 6300 SBCs + -handles logs with extra carriage-returns and linefeeds, such as from certain FTP'ed cases + -handles logs generated/copied from a 'tail' command on the SBC ACLI + -handles MBCD messages in logs, and puts their decoded ascii description in comments in Wireshark + + Issues: + -for very large logs (many megabytes), it takes a long time (many minutes) + -creates fake IP and UDP/TCP headers, which might be misleading + -has to guess sometimes, though it hasn't guessed wrong yet as far as I know + + To-do: + - make it use Struct.tohex/fromhex now that we have the Struct library in Wireshark + - make it use a linux cooked-mode pseudo-header (see http://wiki.wireshark.org/SLL) + - make it use preferences, once I write C-code for Wireshark to do that :) + - rewrite some of the pattern searches to use real regex/PCRE instead? It's not in Wireshark yet, + but it's coming (see https://code.wireshark.org/review/#/c/332/) + +Example SIP over UDP message: +Aug 26 19:25:10.685 On [5:0]2.1.1.1:5060 received from 2.1.2.115:5060 +REGISTER sip:2.1.1.1:5060 SIP/2.0 +Via: SIP/2.0/UDP 2.1.2.115:5060;branch=z9hG4bK6501441021660x81000412 +From: <sip:public_115@2.1.1.1:5060>;tag=520052-7015560x81000412 +To: <sip:public_115@2.1.1.1:5060> +Call-ID: 680192-4234150x81000412@2.1.2.115 +CSeq: 247 REGISTER +Contact: <sip:public_115@2.1.2.115:5060;transport=udp> +Expires: 300 +Max-Forwards: 70 +Authorization: Digest username="public_115",realm="empirix.com",uri="sip:2.1.1.1",response="5d61837cc54dc27018a40f2532e622de",nonce="430f6ff09ecd8c3fdfc5430b6e7e437a4cf77057",algorithm=md5 +Content-Length: 0 + + +---------------------------------------- +Another one: +2007-03-06 13:38:48.037 OPENED +2007-03-06 13:38:48.037 OPENED +2007-03-06 13:38:48.037 OPENED +Mar 6 13:38:54.959 On [1:0]135.25.29.135:5060 received from 192.168.109.138:65471 +OPTIONS sip:135.25.29.135 SIP/2.0 +Accept: application/sdp +User-Agent: ABS GW v5.1.0 +To: sip:135.25.29.135 +From: sip:192.168.109.138;tag=a2a090ade36bb108da70b0c8f7ba02e9 +Contact: sip:192.168.109.138 +Call-ID: 8c0296da4a0d9f4d97e1389cd28d2352@172.16.6.114 +CSeq: 347517161 OPTIONS +Via: SIP/2.0/UDP 192.168.109.138;branch=z9hG4bK21feac80fe9a63c1cf2988baa2af0849 +Max-Forwards: 70 +Content-Length: 0 + + +---------------------------------------- +Another SIP over UDP (from 9200): +File opened. +Jun 8 14:34:22.599 UDP[3:0]10.102.131.194:5060 OPENED +Jun 8 14:34:22.616 UDP[6:0]10.102.130.185:5060 OPENED +Jun 8 14:34:49.416 On [6:0]10.102.130.185:5060 received from 10.102.130.150:5060 +REGISTER sip:csp.noklab.net SIP/2.0 +Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK26b7a48d +From: sip:34903@csp.noklab.net +To: sip:34903@csp.noklab.net +Call-ID: 003094c3-a0160002-23aa7e86-29e5808d@192.168.1.100 +CSeq: 144 REGISTER +User-Agent: CSCO/7 +Contact: <sip:34903@192.168.1.100:5060> +Content-Length: 0 +Expires: 3600 + + +---------------------------------------- + +Example SIP over TCP message (note it ends in the middle of a header name): +Jan 12 00:03:54.700 On 172.25.96.200:8194 received from 172.25.32.28:5060 +SIP/2.0 200 OK +From: Unavailable <sip:Unavailable@172.25.96.200:5060;user=phone>;tag=1200822480 +To: 24001900011 <sip:0011@172.25.32.28:5060;user=phone>;tag=03c86c0b27df1b1254aeccbc000 +Call-ID: 7f6b0887-1d313896-1511da31-b045@144.229.136.111 +CSe +---------------------------------------- + +Example SIP Pre and Post-NAT messages: +Post-NAT from private<realm=e911core> encoded: +SIP/2.0 302 Moved Temporarily +Call-ID: SD27o9f04-fcc63aa885c83e22a1be64cfc210b55e-vjvtv00 +CSeq: 2 INVITE +From: <sip:7866932005@127.1.0.100:5060;user=phone;e911core=TSD5051AEPCORE-dnamt76v6nm04;CKE=BSLD-5cuduig6t52l2;e911vpn=TSD5051AEPVPN-7gdq13vt8fi59>;tag=SD27o9f04-10000000-0-1424021314 +To: <sip:911@127.0.0.100;user=phone;CKE=BSLD-8blt7m3dhnj17>;tag=10280004-0-1239441202 +Via: SIP/2.0/UDP 127.254.254.1:5060;branch=z9hG4bK5i4ue300dgrdras7q281.1 +Server: DC-SIP/1.2 +Content-Length: 0 +Contact: <sip:1111119999@127.0.0.100:5060;e911core=TSD5051AEPCORE-5n86t36uuma01> + + +---------------------------------------- +Pre-NAT to private<realm=e911core> decode: +ACK sip:911@127.0.0.100;user=phone;CKE=BSLD-8blt7m3dhnj17 SIP/2.0 +Via: SIP/2.0/UDP 127.254.254.1:5060;branch=z9hG4bK5i4ue300dgrdras7q281.1 +Call-ID: SD27o9f04-fcc63aa885c83e22a1be64cfc210b55e-vjvtv00 +CSeq: 2 ACK +From: <sip:7866932005@127.1.0.100:5060;user=phone;e911core=TSD5051AEPCORE-dnamt76v6nm04;CKE=BSLD-5cuduig6t52l2;e911vpn=TSD5051AEPVPN-7gdq13vt8fi59>;tag=SD27o9f04-10000000-0-1424021314 +To: <sip:911@127.0.0.100;user=phone;CKE=BSLD-8blt7m3dhnj17>;tag=10280004-0-1239441202 +Max-Forwards: 70 + + +---------------------------------------- + +Example DNS message: +Nov 1 23:03:12.811 On 10.21.232.194:1122 received from 10.21.199.204:53 +DNS Response 3916 flags=8503 q=1 ans=0 auth=1 add=0 net-ttl=0 + Q:NAPTR 7.6.5.4.3.2.1.0.1.2.e164 + NS:SOA e164 ttl=0 netnumber01 + rname=user.netnumber01 + ser=223 ref=0 retry=0 exp=0 minttl=0 + + 0000: 0f 4c 85 03 00 01 00 00 00 01 00 00 01 37 01 36 .L...........7.6 + 0010: 01 35 01 34 01 33 01 32 01 31 01 30 01 31 01 32 .5.4.3.2.1.0.1.2 + 0020: 04 65 31 36 34 00 00 23 00 01 04 65 31 36 34 00 .e164..#...e164. + 0030: 00 06 00 01 00 00 00 00 00 33 0b 6e 65 74 6e 75 .........3.netnu + 0040: 6d 62 65 72 30 31 00 04 75 73 65 72 0b 6e 65 74 mber01..user.net + 0050: 6e 75 6d 62 65 72 30 31 00 00 00 00 df 00 00 00 number01........ + 0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 ............. + +---------------------------------------- +Example MGCP message (note the IP/UDP headers are in the hex): +Mar 1 14:37:26.683 On [0:803]172.16.84.141:2427 sent to 172.16.74.100:2427 +Packet: + 0000: 00 04 00 00 00 01 00 02 00 00 03 23 0a ad 00 c9 ...........#.... + 0010: 45 00 00 a8 23 36 00 00 3c 11 63 fd ac 10 54 8d E...#6..<.c...T. + 0020: ac 10 4a 64 09 7b 09 7b 00 94 16 c2 32 35 30 20 ..Jd.{.{....250 + +250 55363 Connection Deleted +P: PS=6551, OS=1048160, PR=6517, OR=1042720, PL=0, JI=1, LA=5, PC/RPS=6466, PC/ROS=1034560, PC/RPL=0, PC/RJI=0 + +---------------------------------------- +Example MBCD message: +Mar 1 14:37:26.672 On 127.0.0.1:2946 sent to 127.0.0.1:2944 + 0000: ac 3e fd a8 01 01 77 36 9e 00 37 10 0c 34 4c bc .>....w6..7..4L. + 0010: 00 30 23 0c 34 4c bc 00 11 33 00 0e 35 00 04 00 .0#.4L...3..5... + 0020: 00 00 00 30 00 04 00 00 00 00 23 0c 34 4c bd 00 ...0......#.4L.. + 0030: 11 33 00 0e 35 00 04 00 00 00 00 30 00 04 00 00 .3..5......0.... + 0040: 00 00 .. +Transaction = 24589982 { + Context = 204754108 { + Subtract = 204754108 { + Audit { + Stats, + Flow + } + }, + Subtract = 204754109 { + Audit { + Stats, + Flow + } + } + } +} +---------------------------------------- + +]]---------------------------------------- + +-- debug printer, set DEBUG to true to enable printing debug info +-- set DEBUG2 to true to enable really verbose printing +local DEBUG, DEBUG2 = true, false + +local dprint = function() end +local dprint2 = function() end +if DEBUG or DEBUG2 then + dprint = function(...) + print(table.concat({"Lua:", ...}," ")) + end + + if DEBUG2 then + dprint2 = dprint + end +end + +local fh = FileHandler.new("Oracle Acme Packet logs", "acme", "A file reader for Oracle Acme Packet message logs such as sipmsg.log","rs") + + +-- there are certain things we have to create fake state/data for, because they don't exist in the log file +-- for example to create IP headers we have to cerate fake identification field values, and to create +-- timestamps we have to guess the year (and in some cases month/day as well), and for TCP we have +-- to create fake conneciton info, such as sequence numbers. We can't simply have a global static variable holding +-- such things, because Wireshark reads the file sequentially at first, but then calls seek_read for random +-- packets again and we don't want to re-create the fake info again because it will be wrong. So we need to +-- create it for each packet and remember what we created for each packet, so that seek_read gets the same values. +-- We could store the variables in a big table, keyed by the specific header info line for each one; but instead we'll +-- key it off of the file position number, since read() sets it for Wireshark and seek_read() gets it from Wireshark. +-- So we'll have a set of global statics used during read(), but the actual per-packet values will be stored in +-- a table indexed/keyed by the file position number. A separate table holds TCP peer connection info as described later. + +-- the following local table holds global (to this file) static variables that need to be reset every new file read +local statics = { ["ip_ident"] = 0, ["tyear"] = 0, ["tmonth"] = 0, ["tmin"] = 0, ["tmin"] = 0, ["tsec"] = 0, ["tmilli"] = 0, ["nstime"] = NSTime() } + +-- the following table holds per-packet info +-- the key index will be a number - the file position - but it won't be an array type table (too sparse). +-- Each packets entry is a table holding the "static" variables for that packet; this sub-table will be +-- an array style instead of hashmap, to reduce size/performance +-- This table needs to be cleared whenever the file is closed/opened. +local packets = {} +-- the indeces for the variable sub-tables +local IP_IDENT = 1 +local TTIME = 2 +local LOCAL_SEQ = 3 +local REMOTE_SEQ = 4 + +-- the following local table holds TCP peer "connection" info, which is basically +-- TCP control block (TCB) type information; this is needed to create and keep track +-- of fake TCP sockets/headers for messages that went over TCP, for example for fake +-- sequence number info. +-- The key index for this is the local+remote ip:port strings concatenated. +-- The value is a sub-table, array style, holding the most recent sequence numbers. +-- This whole table needs to be cleared whenever the file is closed/opened. +local tcb = {} +-- the indeces for the sub-tables +local TLOCAL_SEQ = 1 +local TREMOTE_SEQ = 2 + +local function reset_state() + tcb = {} + packets = {} + for name, v in pairs(statics) do + statics[name] = 0 + end + statics.nstime = NSTime() +end + +-- helper functions +local char = string.char +local floor = math.floor + +-- takes a Lua number and converts it into a 2-byte string binary (network order) + +local function dec2bin16(num) + return Struct.pack(">I2",num) +end + +-- takes a Lua number and converts it into a 4-byte string binary (network order) +local function dec2bin32(num) + return Struct.pack(">I4",num) +end + + +-- function to skip log info before/between/after messages +local delim = "^%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-$" +-- words that must be found to be skipped. "File ..." is found in 9200 logs) +local skiplist = { " OPENED", " CLOSED", " STARTED", " STOPPED", "^File ", delim } +-- pre/post NAT entries +local pre_nat_header_pattern = "^Pre%-NAT to private<realm=([^>]+)> decode:\r?$" +local post_nat_header_pattern = "^Post%-NAT from private<realm=([^>]+)> encoded:\r?$" + +local function skip_ahead(file, line, position) + repeat + local found = #line == 0 -- will be false unless the line is empty + for i, word in ipairs(skiplist) do + if line:find(word) then + found = true + break + end + end + if found then + position = file:seek() + line = file:read() + if not line then return nil end + elseif line:find(pre_nat_header_pattern) or line:find(post_nat_header_pattern) then + -- skip the whole message + found = true + repeat + line = file:read() + until line:find(delim) + end + until not found + return line, position +end + +-- following pattern grabs month, day, hour, min, sec, millisecs +local header_time_pattern = "^(%u%l%l) ?(%d%d?) (%d%d?):(%d%d):(%d%d)%.(%d%d%d) On " +-- tail'ed file has no month/day +local header_tail_time_pattern = "^(%d%d):(%d%d)%.(%d%d%d) On " + +-- grabs local and remote IPv4:ports (not phy/vlan), and words in between (i.e., "sent to" or "received from") +local header_address_pattern = "(%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?):(%d+) (%l+ %l+) (%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?):(%d+) ?\r?$" +-- grabs local and remote IPv6:ports (not phy/vlan), and words in between (i.e., "sent to" or "received from") +local header_v6address_pattern = "%[([:%x]+)%]:(%d+) (%l+ %l+) %[([:%x]+)%]:(%d+) ?\r?$" + +-- grabs phy/vlan info +local header_phy_pattern = "%[(%d+):(%d+)%]" + +local SENT = 1 +local RECV = 2 +local function get_direction(phrase) + if #phrase == 7 and phrase:find("sent to") then + return SENT + elseif #phrase == 13 and phrase:find("received from") then + return RECV + end + dprint("direction phrase not found") + return nil +end + +-- monthlist table for getting month number value from 3-char name (number is table index) +local monthlist = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} + +local function get_timestamp(line, file_position, seeking) + local i, line_pos, month, day, hour, min, sec, milli = line:find(header_time_pattern) + if not month then + return + end + + if seeking then + -- we've seen this packet before, just go get the saved timestamp + sec = packets[file_position][TTIME] + if not sec then + dprint("failed to get saved timestamp for packet at position:", file_position) + return + end + return sec, line_pos + end + + -- find the month's number + for index, name in ipairs(monthlist) do + if month == name then + month = index + break + end + end + if type(month) ~= "number" then return end + + day = tonumber(day) + hour = tonumber(hour) + min = tonumber(min) + sec = tonumber(sec) + milli = tonumber(milli) + + if not day or not hour or not min or not sec or not milli then + dprint("timestamp could not be determined") + return nil + end + + -- we don't know what year the log file was created, so we have to guess + -- if we guess the current system year, then a log of December loaded in January will appear wrong, + -- as will a log file which lasts over new year + -- so we're going to check the current system month, and if it's less than the log file's then we'll + -- assume the log file started last year; if the system month is larger or equal, then we'll assume the log + -- file is of this year. We only do this checking once per file. + if statics.tyear == 0 then + local curr_year, curr_month = tonumber(os.date("%Y")), tonumber(os.date("%m")) + if curr_month < month then + -- use last year + if curr_year > 0 then + curr_year = curr_year - 1 + end + end + statics.tyear = curr_year + end + + -- if this message's month is less than previous message's, then year wrapped + if month < statics.tmonth then + statics.tyear = statics.tyear + 1 + end + statics.tmonth = month + + local timet = os.time({ ["year"] = statics.tyear, ["month"] = month, ["day"] = day, ["hour"] = hour, ["min"] = min, ["sec"] = sec }) + if not timet then + dprint("timestamp conversion failed") + end + + -- make an NSTime + statics.nstime = NSTime(timet, milli * 1000000) + packets[file_position][TTIME] = statics.nstime + + timet = timet + (milli/1000) + dprint2("found time of ", os.date("%c",timet), " with value=",timet) + + return statics.nstime, line_pos +end + +-- get_tail_time() gets a fictitous timestamp starting from 19:00:00 on Dec 31, 1969, and incrementing based +-- on the minutes/secs/millisecs seen (i.e., if the minute wrapped then hour increases by 1, etc.). +-- this is needed for tail'ed log files, since they don't show month/day/hour +local function get_tail_time(line, file_position, seeking) + local i, line_pos, min, sec, milli = line:find(header_tail_time_pattern) + if not min then return end + + if seeking then + -- we've seen this packet before, just go get the saved timestamp + sec = packets[file_position][TTIME] + if not sec then + dprint("failed to get saved timestamp for packet at position:", file_position) + return + end + return sec, line_pos + end + + min = tonumber(min) + sec = tonumber(sec) + milli = tonumber(milli) + + if not min or not sec or not milli then + dprint("timestamp could not be determined") + return nil + end + + -- get difference in time + local tmin, tsec, tmilli, nstime = statics.tmin, statics.tsec, statics.tmilli, statics.nstime + local ttime = nstime.secs + + -- min, sec, milli are what the log says this tail'ed packet is + -- tmin, tsec, tmilli are what we got from last packet + -- nstime is the unix time of that, and ttime is the seconds of that unix time + + -- if minutes wrapped, or they're equal but seconds wrapped, then handle it as if in the next hour + if (min < tmin) or (min == tmin and sec < tsec) or (min == tmin and sec == tsec and milli < tmilli) then + -- something wrapped, calculate difference as if in next hour + ttime = ttime + (((min * 60) + sec + 3600) - ((tmin * 60) + tsec)) + else + ttime = ttime + (((min * 60) + sec) - ((tmin * 60) + tsec)) + end + statics.tmin, statics.tsec, statics.tmilli = min, sec, milli + statics.nstime = NSTime(ttime, milli * 1000000) + packets[file_position][TTIME] = statics.nstime + + return statics.nstime, line_pos +end + +local hexbin = { + ["0"]=0, ["1"]=1, ["2"]=2, ["3"]=3, ["4"]=4, ["5"]=5, ["6"]=6, ["7"]=7, ["8"]=8, ["9"]=9, ["a"]=10, ["b"]=11, ["c"]=12, ["d"]=13, ["e"]=14, ["f"]=15, + ["00"]=0, ["01"]=1, ["02"]=2, ["03"]=3, ["04"]=4, ["05"]=5, ["06"]=6, ["07"]=7, ["08"]=8, ["09"]=9, ["0a"]=10, ["0b"]=11, ["0c"]=12, ["0d"]=13, ["0e"]=14, ["0f"]=15, + ["10"]=16, ["11"]=17, ["12"]=18, ["13"]=19, ["14"]=20, ["15"]=21, ["16"]=22, ["17"]=23, ["18"]=24, ["19"]=25, ["1a"]=26, ["1b"]=27, ["1c"]=28, ["1d"]=29, ["1e"]=30, ["1f"]=31, + ["20"]=32, ["21"]=33, ["22"]=34, ["23"]=35, ["24"]=36, ["25"]=37, ["26"]=38, ["27"]=39, ["28"]=40, ["29"]=41, ["2a"]=42, ["2b"]=43, ["2c"]=44, ["2d"]=45, ["2e"]=46, ["2f"]=47, + ["30"]=48, ["31"]=49, ["32"]=50, ["33"]=51, ["34"]=52, ["35"]=53, ["36"]=54, ["37"]=55, ["38"]=56, ["39"]=57, ["3a"]=58, ["3b"]=59, ["3c"]=60, ["3d"]=61, ["3e"]=62, ["3f"]=63, + ["40"]=64, ["41"]=65, ["42"]=66, ["43"]=67, ["44"]=68, ["45"]=69, ["46"]=70, ["47"]=71, ["48"]=72, ["49"]=73, ["4a"]=74, ["4b"]=75, ["4c"]=76, ["4d"]=77, ["4e"]=78, ["4f"]=79, + ["50"]=80, ["51"]=81, ["52"]=82, ["53"]=83, ["54"]=84, ["55"]=85, ["56"]=86, ["57"]=87, ["58"]=88, ["59"]=89, ["5a"]=90, ["5b"]=91, ["5c"]=92, ["5d"]=93, ["5e"]=94, ["5f"]=95, + ["60"]=96, ["61"]=97, ["62"]=98, ["63"]=99, ["64"]=100, ["65"]=101, ["66"]=102, ["67"]=103, ["68"]=104, ["69"]=105, ["6a"]=106, ["6b"]=107, ["6c"]=108, ["6d"]=109, ["6e"]=110, ["6f"]=111, + ["70"]=112, ["71"]=113, ["72"]=114, ["73"]=115, ["74"]=116, ["75"]=117, ["76"]=118, ["77"]=119, ["78"]=120, ["79"]=121, ["7a"]=122, ["7b"]=123, ["7c"]=124, ["7d"]=125, ["7e"]=126, ["7f"]=127, + ["80"]=128, ["81"]=129, ["82"]=130, ["83"]=131, ["84"]=132, ["85"]=133, ["86"]=134, ["87"]=135, ["88"]=136, ["89"]=137, ["8a"]=138, ["8b"]=139, ["8c"]=140, ["8d"]=141, ["8e"]=142, ["8f"]=143, + ["90"]=144, ["91"]=145, ["92"]=146, ["93"]=147, ["94"]=148, ["95"]=149, ["96"]=150, ["97"]=151, ["98"]=152, ["99"]=153, ["9a"]=154, ["9b"]=155, ["9c"]=156, ["9d"]=157, ["9e"]=158, ["9f"]=159, + ["a0"]=160, ["a1"]=161, ["a2"]=162, ["a3"]=163, ["a4"]=164, ["a5"]=165, ["a6"]=166, ["a7"]=167, ["a8"]=168, ["a9"]=169, ["aa"]=170, ["ab"]=171, ["ac"]=172, ["ad"]=173, ["ae"]=174, ["af"]=175, + ["b0"]=176, ["b1"]=177, ["b2"]=178, ["b3"]=179, ["b4"]=180, ["b5"]=181, ["b6"]=182, ["b7"]=183, ["b8"]=184, ["b9"]=185, ["ba"]=186, ["bb"]=187, ["bc"]=188, ["bd"]=189, ["be"]=190, ["bf"]=191, + ["c0"]=192, ["c1"]=193, ["c2"]=194, ["c3"]=195, ["c4"]=196, ["c5"]=197, ["c6"]=198, ["c7"]=199, ["c8"]=200, ["c9"]=201, ["ca"]=202, ["cb"]=203, ["cc"]=204, ["cd"]=205, ["ce"]=206, ["cf"]=207, + ["d0"]=208, ["d1"]=209, ["d2"]=210, ["d3"]=211, ["d4"]=212, ["d5"]=213, ["d6"]=214, ["d7"]=215, ["d8"]=216, ["d9"]=217, ["da"]=218, ["db"]=219, ["dc"]=220, ["dd"]=221, ["de"]=222, ["df"]=223, + ["e0"]=224, ["e1"]=225, ["e2"]=226, ["e3"]=227, ["e4"]=228, ["e5"]=229, ["e6"]=230, ["e7"]=231, ["e8"]=232, ["e9"]=233, ["ea"]=234, ["eb"]=235, ["ec"]=236, ["ed"]=237, ["ee"]=238, ["ef"]=239, + ["f0"]=240, ["f1"]=241, ["f2"]=242, ["f3"]=243, ["f4"]=244, ["f5"]=245, ["f6"]=246, ["f7"]=247, ["f8"]=248, ["f9"]=249, ["fa"]=250, ["fb"]=251, ["fc"]=252, ["fd"]=253, ["fe"]=254, ["ff"]=255 +} + +local function iptobytes(ipaddr) + local bytes = { ipaddr:match("(%d+)%.(%d+)%.(%d+)%.(%d+)") } + if not #bytes == 4 then + dprint("failed to get ip address bytes for '", ipaddr, "'") + return + end + local ip = "" + for i, byte in ipairs(bytes) do + ip = ip .. char(tonumber(byte)) + end + return ip +end + +local function hexword2bin(word) + if #word == 4 then + return char(hexbin[word:sub(1,2)], hexbin[word:sub(3,4)]) + elseif #word == 3 then + return char(hexbin[word:sub(1,1)], hexbin[word:sub(2,3)]) + elseif #word < 3 then + return char(0, hexbin[word]) + end + return nil -- error +end + +-- convert this 2620:0:60:8ac::102 to its 16-byte binary (=8 of 2-byte words) +local NUMWORDS = 8 +local function ipv6tobytes(ipaddr) + -- start with all 16 bytes being zeroes + local words = { "\00\00", "\00\00", "\00\00", "\00\00", "\00\00", "\00\00", "\00\00", "\00\00" } + -- now walk from front of ipv6 address string replacing byte numbers above; + -- if we hit a "::", then jump to end and do it in reverse + local colon_s, colon_e = ipaddr:find("::%x") + if colon_s then + -- there's a double-colon, so split the string and do the end first, backwards + -- get each chunk first + local t = {} + local index, wordix = 1, NUMWORDS + for w in string.gmatch(ipaddr:sub(colon_e - 1), ":(%x+)") do + t[index] = hexword2bin(w) + index = index + 1 + end + for ix=index-1, 1, -1 do + words[wordix] = t[ix] + wordix = wordix - 1 + end + ipaddr = ipaddr:sub(1, colon_s) + end + + local i = 1 + for w in string.gmatch(ipaddr, "(%x+):?") do + words[i] = hexword2bin(w) + i = i + 1 + end + + if not #words == NUMWORDS then + dprint("failed to get IPv6 address bytes for '", ipaddr, "'") + return + end + + return table.concat(words) +end + +-- calculates checksum as done for IP, TCP, UDP +local function checksum(chunk) + local sum = 0 + -- take every 2-byte value and add them up + for one, two in chunk:gmatch("(.)(.)") do + sum = sum + (string.byte(one) * 256) + (string.byte(two)) + while floor(sum / 65536) > 0 do + -- add carry/overflow value + sum = (sum % 65536) + (floor(sum / 65536)) + end + end + + -- now get one's complement of that + sum = 65535 - sum + + -- and return it as a 2-byte string + return dec2bin16(sum) +end + +---------------------------------------- +-- protocol type number +local PROTO_UDP = "\17" +local PROTO_TCP = "\06" +-- enum +local IPv4 = 1 +local IPv6 = 2 +-- both type enums and header lengths +local UDP = 8 +local TCP = 20 + +---------------------------------------- +-- Packet creation/serialization occurs using a Lua class object model +-- There's a single base class 'Packet' which has data/methods every packet type has +-- 'RawPacket' and 'DataPacket' both derive from 'Packet'. +-- 'RawPacket' is for packets which the log file has the raw IP/UDP headers for, +-- such as ALG log messages (MGCP/NCS). Since the IP headers are in them, we use those. +-- 'DataPacket' is for packets which the log file only has payload data for, and +-- we need to create fake IP/UDP or IP/TCP headers for. +-- 'BinPacket' and'AsciiPacket' both derive from 'DataPacket'. +-- 'BinPacket' is for binary-style logged packets, such as MBCD or DNS, while +-- 'AsciiPacket' is for ascii-style ones such as SIP. +-- 'DnsPacket' derives from 'BinPacket', for DNS-style logs. + +-- Each class has a read_data() method, which reads in the packet data, builds the packet, +-- and sets the Wireshark buffer. Some classes have a get_data() method which read_data() +-- calls, to get the payload data before building a fake packet. + +-- The base Packet class has a get_hex_data() and get_ascii_data() methods, to get the payload +-- in either form, and those base methods are called by get_data() or read_data() of derived +-- classes. + +-- For performance reasons, packet data is read line-by-line into a table (called bufftbl), +-- which is concatendated at the end. This avoids Lua building interim strings and garbage +-- collecting them. But it makes the code uglier. The get_data()/get_hex_data()/get_ascii_data() +-- methods read into this table they get passed, while the read_data() functions handle managing +-- the table. + +---------------------------------------- +---------------------------------------- +-- The base Packet class, from which others derive +-- all Packets have a ptype, timestamp, source and dest address:port, and data +-- +local Packet = {} +local Packet_mt = { __index = Packet } + +function Packet:new(timestamp, direction, source_ip, source_port, dest_ip, dest_port, ptype, ttype, file_position) + local new_class = { -- the new instance + ["timestamp"] = timestamp, + ["direction"] = direction, + ["source_ip"] = source_ip, + ["source_port"] = source_port, + ["dest_ip"] = dest_ip, + ["dest_port"] = dest_port, + ["ptype"] = ptype, + ["ttype"] = ttype, + ["file_position"] = file_position + } + setmetatable( new_class, Packet_mt ) -- all instances share the same metatable + return new_class +end + +function Packet:set_comment(comment) + self["comment"] = comment +end + +function Packet:set_wslua_fields(frame) + frame.time = self.timestamp + frame.flags = wtap_presence_flags.TS -- for timestamp + if self.comment then + frame.comment = self.comment + frame.flags = frame.flags + wtap_presence_flags.COMMENTS -- comment flag + end + return true +end + +local packet_hexline_pattern = "^ %x%x%x0: %x%x" +function Packet:get_hex_data(file, line, bufftbl, index) + local start = index + + dprint2("Packet:get_hex_data() called") + repeat + for word in line:gmatch("(%x%x) ") do + bufftbl[index] = char(hexbin[word]) + index = index + 1 + if ((index - start) % 16) == 0 then break end + end + line = file:read() + until not line or not line:find(packet_hexline_pattern) + + return index - start, line +end + +function Packet:get_ascii_data(file, line, bufftbl, index, only_newline) + local bufflen = 0 -- keep tally of total length of payload + local found_delim = true + + dprint2("Packet:get_ascii_data() called") + repeat + bufftbl[index] = line + bufflen = bufflen + #line + + -- sanity check if line has "\r" at end, and if so only add \n + if line:find("\r",-1,true) then + bufftbl[index+1] = "\n" + bufflen = bufflen + 1 + dprint2("Found carriage-return at end of line") + elseif only_newline then + -- only add a newline + bufftbl[index+1] = "\n" + bufflen = bufflen + 1 + else + bufftbl[index+1] = "\r\n" + bufflen = bufflen + 2 + end + index = index + 2 + + -- read next line now + line = file:read() + if not line then + -- hit eof? + found_delim = false + break + end + + until line:find(delim) + + -- get rid of last \r\n, if we found a dashed delimeter, as it's not part of packet + if found_delim then + bufflen = bufflen - bufftbl[index-1]:len() + bufftbl[index-1] = nil + end + + return bufflen +end + +---------------------------------------- +-- RawPacket class, for packets that the log file contains the whole IP header for, such as algd logs +-- +local RawPacket = {} +local RawPacket_mt = { __index = RawPacket } +setmetatable( RawPacket, Packet_mt ) -- make RawPacket inherit from Packet + +function RawPacket:new(...) + local new_class = Packet:new(...) -- the new instance + setmetatable( new_class, RawPacket_mt ) -- all instances share the same metatable + return new_class +end + +function RawPacket:read_data(file, frame, line, seeking) + local bufftbl = {} -- table to hold data bytes + local index = 1 -- start at first slot in array + + -- need to skip "Packet:" line and first 0000: line, it's internal junk + line = file:read() + line = file:read() + + dprint2("RawPacket:read_data() getting hex from line='", line, "'") + local bufflen, line = self:get_hex_data(file, line, bufftbl, index) + if not bufflen or bufflen < 21 then + dprint("error getting binary data") + return false + end + + -- add remainder as more packet data, but first delete overlap + -- see if frag bits are set in IP header, to see if UDP/TCP header exists + if self.ptype == IPv4 then + -- grab byte with frag flags and first byte of offset + local flag = string.byte(bufftbl[7]) -- converts binary character to number + local frag_offset = flag % 32 -- masks off upper 3 bits + frag_offset = (frag_offset * 256) + string.byte(bufftbl[8]) + flag = floor(flag / 224) -- shift right + flag = flag % 2 -- mask upper bits + if flag == 1 or frag_offset > 0 then + -- we have a fragmented IPv4 packet, so no proto header + -- only save first 20 bytes (the IP header) + for i=bufflen, 21, -1 do + bufftbl[i] = nil + end + bufflen = 20 + else + -- only save first 20 + proto size bytes + local save + if bufftbl[10] == PROTO_UDP then + save = 28 + elseif bufftbl[10] == PROTO_TCP then + save = 40 + else + dprint("failed to fix raw packet overlap") + return + end + for i=bufflen, save+1, -1 do + bufftbl[i] = nil + end + bufflen = save + end + end + -- TODO: IPv6 + + -- now read in rest of message, if any + -- first skip extra empty newline + if #line == 0 then + line = file:read() + end + + bufflen = bufflen + self:get_ascii_data(file, line, bufftbl, bufflen+1, true) + + frame.data = table.concat(bufftbl) + + return true +end + +---------------------------------------- +-- DataPacket class, for packets that the log file contains just the payload data for +-- +local DataPacket = {} +local DataPacket_mt = { __index = DataPacket } +setmetatable( DataPacket, Packet_mt ) -- make Dataacket inherit from Packet + +function DataPacket:new(...) + local new_class = Packet:new(...) -- the new instance + setmetatable( new_class, DataPacket_mt ) -- all instances share the same metatable + return new_class +end + +function DataPacket:set_tcbkey(key) + self["tcbkey"] = key + return +end + +function DataPacket:build_ipv4_hdr(bufflen, proto, seeking) + local len = bufflen + 20 -- 20 byte IPv4 header size + + -- figure out the ip identification value + local ip_ident + if seeking then + ip_ident = packets[self.file_position][IP_IDENT] + else + -- increment ident value + statics.ip_ident = statics.ip_ident + 1 + if statics.ip_ident == 65536 then + statics.ip_ident = 1 + end + ip_ident = statics.ip_ident + -- save it for future seeking + packets[self.file_position][IP_IDENT] = ip_ident + end + + -- use a table to concatenate as it's slightly faster that way + local hdrtbl = { + "\69\00", -- 1=ipv4 and 20 byte header length + dec2bin16(len), -- 2=packet length bytes + dec2bin16(ip_ident), -- 3=ident field bytes + "\00\00\64", -- 4=flags/fragment offset, ttl + proto, -- 5=proto + "\00\00", -- 6=checksum (using zero for now) + iptobytes(self.source_ip), -- 7=source ip + iptobytes(self.dest_ip) -- 8=dest ip + } + + -- calc IPv4 header checksum, and set its value + hdrtbl[6] = checksum(table.concat(hdrtbl)) + + return table.concat(hdrtbl) +end + +function DataPacket:build_ipv6_hdr(bufflen, proto) + -- use a table to concatenate as it's slightly faster that way + local hdrtbl = { + "\96\00\00\00", -- 1=ipv6 version, class, label + dec2bin16(bufflen), -- 2=packet length bytes + proto .. "\64", -- 4=proto, ttl + ipv6tobytes(self.source_ip), -- 5=source ip + ipv6tobytes(self.dest_ip) -- 6=dest ip + } + return table.concat(hdrtbl) +end + +-- calculates TCP/UDP header checksums with pseudo-header info +function DataPacket:calc_header_checksum(bufftbl, bufflen, hdrtbl, proto) + -- first create pseudo IP header + if self.ptype == IPv4 then + local iphdrtbl = { + iptobytes(self.source_ip), -- 1=source ip + iptobytes(self.dest_ip), -- 2=dest ip + "\00", -- zeros + proto, -- proto + dec2bin16(bufflen) -- payload length bytes + } + bufftbl[1] = table.concat(iphdrtbl) + elseif self.ptype == IPv6 then + local iphdrtbl = { + ipv6tobytes(self.source_ip), -- 1=source ip + ipv6tobytes(self.dest_ip), -- 2=dest ip + "\00\00", -- zeroes + dec2bin16(bufflen), -- payload length bytes + "\00\00\00", -- zeros + proto -- proto + } + bufftbl[1] = table.concat(iphdrtbl) + end + + -- and pseudo TCP or UDP header + bufftbl[2] = table.concat(hdrtbl) + + -- see if payload is odd length + local odd = false + if bufflen % 2 == 1 then + -- odd number of payload bytes, add zero byte at end + odd = true -- remember to undo this + bufftbl[#bufftbl+1] = "\00" + end + + local result = checksum(table.concat(bufftbl)) + + -- remove pseudo-headers + bufftbl[1] = nil + bufftbl[2] = nil + if odd then + bufftbl[#bufftbl] = nil + end + + return result +end + + +function DataPacket:build_udp_hdr(bufflen, bufftbl) + local len = bufflen + 8 -- 8 for size of UDP header + local hdrtbl = { + dec2bin16(self.source_port), -- 1=source port bytes + dec2bin16(self.dest_port), -- 2=dest port bytes + dec2bin16(len), -- 3=payload length bytes + "\00\00" -- 4=checksum + } + if bufftbl then + -- calc udp checksum (only done for IPv6) + hdrtbl[4] = self:calc_header_checksum(bufftbl, len, hdrtbl, PROTO_UDP) + end + return table.concat(hdrtbl) +end + + +function DataPacket:build_tcp_hdr(bufflen, bufftbl, seeking) + local len = bufflen + 20 -- 20 for size of TCP header + + local local_seq, remote_seq + if seeking then + local_seq = packets[self.file_position][LOCAL_SEQ] + remote_seq = packets[self.file_position][REMOTE_SEQ] + else + -- find socket/tcb info for this "stream", create if not found + if not tcb[self.tcbkey] then + -- create them + tcb[self.tcbkey] = {} + local_seq = 1 + remote_seq = 1 + packets[self.file_position][LOCAL_SEQ] = 1 + packets[self.file_position][REMOTE_SEQ] = 1 + -- set tcb to next sequence numbers, so that the correct "side" + -- acknowledges receiving these bytes + if self.direction == SENT then + -- this packet is being sent, so local sequence increases next time + tcb[self.tcbkey][TLOCAL_SEQ] = bufflen+1 + tcb[self.tcbkey][TREMOTE_SEQ] = 1 + else + -- this packet is being received, so remote sequence increases next time + -- and local side will acknowldge it next time + tcb[self.tcbkey][TLOCAL_SEQ] = 1 + tcb[self.tcbkey][TREMOTE_SEQ] = bufflen+1 + end + else + -- stream already exists, so send the current tcb seqs and update for next time + if self.direction == SENT then + -- this packet is being sent, so local sequence increases next time + local_seq = tcb[self.tcbkey][TLOCAL_SEQ] + remote_seq = tcb[self.tcbkey][TREMOTE_SEQ] + tcb[self.tcbkey][TLOCAL_SEQ] = local_seq + bufflen + else + -- this packet is being received, so the "local" seq number of the packet is the remote's seq really + local_seq = tcb[self.tcbkey][TREMOTE_SEQ] + remote_seq = tcb[self.tcbkey][TLOCAL_SEQ] + -- and remote seq needs to increase next time (remember local_seq is TREMOTE_SEQ) + tcb[self.tcbkey][TREMOTE_SEQ] = local_seq + bufflen + end + packets[self.file_position][LOCAL_SEQ] = local_seq + packets[self.file_position][REMOTE_SEQ] = remote_seq + end + end + + local hdrtbl = { + dec2bin16(self.source_port), -- 1=source port bytes + dec2bin16(self.dest_port), -- 2=dest port bytes + dec2bin32(local_seq), -- 3=sequence + dec2bin32(remote_seq), -- 4=ack number + "\80\16\255\255", -- 5=offset, flags, window size + "\00\00", -- 6=checksum + "\00\00" -- 7=urgent pointer + } + + -- calc tcp checksum + hdrtbl[6] = self:calc_header_checksum(bufftbl, len, hdrtbl, PROTO_TCP) + + return table.concat(hdrtbl) +end + +function DataPacket:build_packet(bufftbl, bufflen, seeking) + dprint2("DataPacket:build_packet() called") + if self.ptype == IPv4 then + if self.ttype == UDP then + bufftbl[2] = self:build_udp_hdr(bufflen) + bufftbl[1] = self:build_ipv4_hdr(bufflen + 8, PROTO_UDP, seeking) + elseif self.ttype == TCP then + bufftbl[2] = self:build_tcp_hdr(bufflen, bufftbl, seeking) + bufftbl[1] = self:build_ipv4_hdr(bufflen + 20, PROTO_TCP, seeking) + end + elseif self.ptype == IPv6 then + -- UDP for IPv6 requires checksum calculation, so we can't avoid more work + if self.ttype == UDP then + bufftbl[2] = self:build_udp_hdr(bufflen, bufftbl) + bufftbl[1] = self:build_ipv6_hdr(bufflen + 8, PROTO_UDP) + elseif self.ttype == TCP then + bufftbl[2] = self:build_tcp_hdr(bufflen, bufftbl, seeking) + bufftbl[1] = self:build_ipv6_hdr(bufflen + 20, PROTO_TCP) + end + else + dprint("DataPacket:build_packet: invalid packet type (neither IPv4 nor IPv6)") + return nil + end + + return table.concat(bufftbl) +end + +-- for performance, we read each line into a table and concatenate it at end +-- but it makes this code super ugly +function DataPacket:read_data(file, frame, line, seeking) + local bufftbl = { "", "" } -- 2 slots for ip and udp/tcp headers + local index = 3 -- start at third slot in array + local comment -- for any packet comments + + dprint2("DataPacket: read_data(): calling get_data") + local bufflen = self:get_data(file, line, bufftbl, index) + if not bufflen then + dprint("DataPacket: error getting ascii or binary data") + return false + end + + local buff = self:build_packet(bufftbl, bufflen, seeking) + + frame.data = buff + + return true +end + + +---------------------------------------- +-- BinPacket class, for packets that the log file contains binary payload data for, such as MBCD +-- +local BinPacket = {} +local BinPacket_mt = { __index = BinPacket } +setmetatable( BinPacket, DataPacket_mt ) -- make BinPacket inherit from DataPacket + +function BinPacket:new(...) + local new_class = DataPacket:new(...) -- the new instance + setmetatable( new_class, BinPacket_mt ) -- all instances share the same metatable + return new_class +end + +function BinPacket:get_comment_data(file, line, stop_pattern) + local comments = {} + + while line and not line:find(stop_pattern) do + if #line > 0 then + comments[#comments+1] = line + comments[#comments+1] = "\r\n" + end + line = file:read() + end + + if #comments > 0 then + -- get rid of extra "\r\n" + comments[#comments] = nil + self:set_comment(table.concat(comments)) + end + + return line +end + +function BinPacket:get_data(file, line, bufftbl, index) + local is_alg = false + + local bufflen, line = self:get_hex_data(file, line, bufftbl, index) + + -- now eat rest of message until delimeter or end of file + -- we'll put them in comments + line = self:get_comment_data(file, line, delim) + + -- return the bufflen, which is the same as number of table entries we made + return bufflen +end + +---------------------------------------- +-- DnsPacket class, for DNS packets (which are binary but with commments at top) +-- +local DnsPacket = {} +local DnsPacket_mt = { __index = DnsPacket } +setmetatable( DnsPacket, BinPacket_mt ) -- make DnsPacket inherit from BinPacket + +function DnsPacket:new(...) + local new_class = BinPacket:new(...) -- the new instance + setmetatable( new_class, DnsPacket_mt ) -- all instances share the same metatable + return new_class +end + +local binpacket_start_pattern = "^ 0000: %x%x %x%x %x%x %x%x %x%x %x%x %x%x %x%x " +function DnsPacket:get_data(file, line, bufftbl, index) + -- it's UDP regardless of what parse_header() thinks + self.ttype = UDP + + -- comments are at top instead of bottom of messsage + line = self:get_comment_data(file, line, binpacket_start_pattern) + + local bufflen, line = self:get_hex_data(file, line, bufftbl, index) + + -- now eat rest of message until delimeter or end of file + while line and not line:find(delim) do + line = file:read() + end + + -- return the bufflen, which is the same as number of table entries we made + return bufflen +end + +---------------------------------------- +-- AsciiPacket class, for packets that the log file contains ascii payload data for +-- +local AsciiPacket = {} +local AsciiPacket_mt = { __index = AsciiPacket } +setmetatable( AsciiPacket, DataPacket_mt ) -- make AsciiPacket inherit from DataPacket + +function AsciiPacket:new(...) + local new_class = DataPacket:new(...) -- the new instance + setmetatable( new_class, AsciiPacket_mt ) -- all instances share the same metatable + return new_class +end + +function AsciiPacket:get_data(file, line, bufftbl, index) + return self:get_ascii_data(file, line, bufftbl, index) +end + + +---------------------------------------- +-- To determine packet type, we peek at the first line of 'data' following the log +-- message header. Its pattern determines the Packet object type. +-- The following are the patterns we look for; if it doesn't match one of these, +-- then it's an AsciiPacket: +local packet_patterns = { + { "^ 0000: %x%x %x%x %x%x %x%x %x%x %x%x %x%x %x%x ", BinPacket }, + { "^Packet:$", RawPacket }, + { "^DNS Query %d+ flags=%d+ q=%d+ ans=%d+", DnsPacket }, + { "^DNS Response %d+ flags=%d+ q=%d+ ans=%d+", DnsPacket } +} +-- indeces for above +local PP_PATTERN = 1 +local PP_CLASS = 2 + +local function get_packet_class(line) + for i, t in ipairs(packet_patterns) do + if line:find(t[PP_PATTERN]) then + dprint2("got class type=",i) + return t[PP_CLASS] + end + end + dprint2("got class type AsciiPacket") + return AsciiPacket +end + +---------------------------------------- +-- parses header line +-- returns nil on failure +-- the header lines look like this: +-- Aug 10 14:30:11.134 On [1:544]10.201.145.237:5060 received from 10.210.1.193:5060 +-- this one has no phy/vlan info in brackets: +-- Mar 6 13:39:06.122 On 127.0.0.1:2945 sent to 127.0.0.1:2944 +-- this one is IPv6: +-- Aug 10 14:30:11.140 On [3:0][2620:0:60:8ac::102]:5060 sent to [2620:0:60:8ab::12]:5060 +-- this is from a tail'ed log output: +-- 52:22.434 On [0:0]205.152.56.211:5060 received from 205.152.56.75:5060 +local loopback_pattern = "^127%.0%.0%.%d+$" +local function parse_header(file, line, file_position, seeking) + + if seeking then + -- verify we've seen this packet before + if not packets[file_position] then + dprint("parse_header: packet at file position ", file_position, " not saved previously") + return + end + else + -- first time through, create sub-table for the packet + packets[file_position] = {} + end + + -- get time info, and line match ending position + local timestamp, line_pos = get_timestamp(line, file_position, seeking) + if not timestamp then + -- see if it's a tail'ed log instead + timestamp, line_pos = get_tail_time(line, file_position, seeking) + end + + if not timestamp then + dprint("parse_header: could not parse time portion") + return + end + + local ptype, ttype = IPv4, UDP + + -- get phy/vlan if present + -- first skip past time portion + local phy, vlan, i, j, k + line_pos = line_pos + 1 + i, j, phy, vlan = line:find(header_phy_pattern, line_pos) + if i then + phy = tonumber(phy) + vlan = tonumber(vlan) + line_pos = j -- skip past this portion for next match + else + -- if there's no phy/vlan info, then assume it's TCP (unless it's loopback address we'll check later) + ttype = TCP + end + + -- get addresses and direction + local local_ip, local_port, direction, remote_ip, remote_port = line:match(header_address_pattern, line_pos) + if not local_ip then + -- try IPv6 + local_ip, local_port, direction, remote_ip, remote_port = line:match(header_v6address_pattern, line_pos) + if not local_ip then + dprint("parse_header: could not parse address portion") + return nil + end + ptype = IPv6 + end + + if local_ip:find(loopback_pattern) and remote_ip:find(loopback_pattern) then + -- internal loopback packets never have phy/vlan but are always UDP messages (for all intents) + ttype = UDP + end + + direction = get_direction(direction) + if direction == nil then + dprint("parse_header: failed to convert direction") + return nil + end + + local source_ip, source_port, dest_ip, dest_port = local_ip, local_port, remote_ip, remote_port + if direction == RECV then + -- swap them + source_ip, source_port, dest_ip, dest_port = remote_ip, remote_port, local_ip, local_port + end + -- convert + source_port = tonumber(source_port) + dest_port = tonumber(dest_port) + + -- peek at next line to determine packet type + local position = file:seek() + line = file:read() + dprint2("parse_header: peeking at line='", line, "'") + packet_class = get_packet_class(line) + file:seek("set", position) -- go back + + local packet = packet_class:new(timestamp, direction, source_ip, source_port, dest_ip, dest_port, ptype, ttype, file_position) + if not packet then + dprint("parse_header: parser failed to create Packet object") + end + + if ttype == TCP then + -- if the packet is tcp type, then set the key for TCB table lookup + packet:set_tcbkey(table.concat({ "[", local_ip, "]:", local_port, "->[", remote_ip, "]:", remote_port })) + end + + return packet +end + + +---------------------------------------- +-- file handling functions for Wireshark to use + +-- The read_open is called by Wireshark once per file, to see if the file is this reader's type. +-- Since there is no exact magic sequence to search for, we have to use heuristics to guess if the file +-- is our type or not, which we do by parsing a message header. +-- Since Wireshark uses the file cursor position for future reading of this file, we also have to seek back to the beginning +-- so that our normal read() function works correctly. +local function read_open(file, capture) + -- save current position to return later + local position = file:seek() + local line = file:read() + if not line then return false end + dprint2("read_open: got this line begin:\n'", line, "'") + + line, position = skip_ahead(file, line, position) + if not line then return false end + dprint2("read_open: got this line after skip:\n'", line, "', with position=", position) + + if parse_header(file, line, position) then + file:seek("set",position) + capture.time_precision = wtap_filetypes.TSPREC_MSEC -- for millisecond precision + capture.encap = wtap.RAW_IP -- whole file is raw IP format + capture.snapshot_length = 0 -- unknown snaplen + capture.comment = "Oracle Acme Packet SBC message log" + capture.os = "VxWorks or Linux" + capture.hardware = "Oracle Acme Packet SBC" + -- reset static variables + reset_state() + return true + end + + return false +end + +---------------------------------------- +-- this is used by both read() and seek_read() +local function read_common(funcname, file, frame, position, seeking) + + local line = file:read() + if not line then + dprint(funcname, "hit end of file") + return false + end + line, position = skip_ahead(file, line, position) + if not line then + if file:read(0) ~= nil then + dprint(funcname, "did not hit end of file after skipping but ending anyway") + else + dprint2(funcname, "hit end of file after skipping") + end + return false + end + + dprint2(funcname, ": parsing line='", line, "'") + local phdr = parse_header(file, line, position, seeking) + if not phdr then + dprint(funcname, "failed to parse header") + return false + end + + line = file:read() + + dprint2(funcname,": calling class object's read_data()") + phdr:read_data(file, frame, line, seeking) + + if not phdr:set_wslua_fields(frame) then + dprint(funcname, "failed to set Wireshark packet header info") + return + end + return position +end + +---------------------------------------- +-- Wireshark/tshark calls read() for each frame/record in the file +-- It passes in a File object and FrameInfo object to this function +-- It expects in return the file offset position the record starts at, +-- or nil/false if there's an error or end-of-file is reached. +-- The offset position is used later: wireshark remembers it and gives +-- it to seek_read() at various random times +local function read(file, frame) + local position = file:seek() + position = read_common("read", file, frame, position) + if not position then + if file:read(0) ~= nil then + dprint("read failed to call read_common") + else + dprint2("read: reached end of file") + end + return false + end + return position +end + +---------------------------------------- +-- Wireshark/tshark calls seek_read() for each frame/record in the file, at random times +-- It passes in to this function a File object, FrameInfo object, and the offset position number +-- It expects in return true for successful parsing, or nil/false if there's an error. +local function seek_read(file, frame, offset) + file:seek("set",offset) + if not read_common("seek_read", file, frame, offset, true) then + dprint("seek_read failed to call read_common") + return false + end + return true +end + +---------------------------------------- +-- Wireshark/tshark calls read_close() when it's closing the file completely +-- this is a good opportunity to clean up any state you may have created during +-- file reading. (in our case there *is* state to reset) +local function read_close(file) + reset_state() + return true +end + +---------------------------------------- +-- An often unused function, Wireshark calls this when the sequential walk-through is over +-- (i.e., no more calls to read(), only to seek_read()). So we'll clear the TCB table +-- here to free up memory; this is undoubtedly unecessary, but good practice. +local function seq_read_close(file) + tcb = {} + return true +end + +-- set above functions to the FileHandler +fh.read_open = read_open +fh.read = read +fh.seek_read = seek_read +fh.read_close = read_close +fh.seq_read_close = seq_read_close +fh.extensions = "log" -- this is just a hint + +-- and finally, register the FileHandler! +register_filehandler(fh) diff --git a/test/lua/pcap_file.lua b/test/lua/pcap_file.lua new file mode 100644 index 0000000000..3f3367880d --- /dev/null +++ b/test/lua/pcap_file.lua @@ -0,0 +1,548 @@ +-- pcap_file_reader.lua +-------------------------------------------------------------------------------- +--[[ + This is a Wireshark Lua-based pcap capture file reader. + Author: Hadriel Kaplan + + This "capture file" reader reads pcap files - the old style ones. Don't expect this to + be as good as the real thing; this is a simplistic implementation to show how to + create such file readers, and for testing purposes. + + This script requires Wireshark v1.11.3 or newer. +--]] +-------------------------------------------------------------------------------- + +local wireshark_name = "Wireshark" +if not GUI_ENABLED then + wireshark_name = "Tshark" +end + +-- verify Wireshark is new enough +local major, minor, micro = get_version():match("(%d+)%.(%d+)%.(%d+)") +if major and tonumber(major) <= 1 and ((tonumber(minor) <= 10) or (tonumber(minor) == 11 and tonumber(micro) < 3)) then + error( "Sorry, but your " .. wireshark_name .. " version (" .. get_version() .. ") is too old for this script!\n" .. + "This script needs " .. wireshark_name .. "version 1.11.3 or higher.\n" ) +end + +-- verify we have the Struct library in wireshark +-- technically we should be able to do this with 'require', but Struct is a built-in +assert(Struct.unpack, wireshark_name .. " does not have the Struct library!") + +-- debug printer, set DEBUG to true to enable printing debug info +-- set DEBUG2 to true to enable really verbose printing +local DEBUG, DEBUG2 = false, false + +local dprint = function() end +local dprint2 = function() end +if DEBUG or DEBUG2 then + dprint = function(...) + print(table.concat({"Lua:", ...}," ")) + end + + if DEBUG2 then + dprint2 = dprint + end +end + +---------------------------------------- +-- to make it easier to read this file, we'll define some of the functions +-- later on, but we need them earlier, so we "declare" them here +local parse_file_header, parse_rec_header, read_common + + +-- these will be set inside of parse_file_header(), but we're declaring them up here +local VERSION_MAJOR = 2 +local VERSION_MINOR = 4 +local TIMEZONE = 0 +local SIGFIGS = 0 +local SNAPLEN = 0 +local ENCAP_TYPE = wtap.UNKNOWN + +-------------------------------------------------------------------------------- +-- file reader handling functions for Wireshark to use +-------------------------------------------------------------------------------- + +---------------------------------------- +-- The read_open() is called by Wireshark once per file, to see if the file is this reader's type. +-- Wireshark passes in a File object to this function +-- It expects in return either nil or false to mean it's not our file type, or true if it is +-- In our case what this means is we figure out if the file has the magic header, and get the +-- endianess of the file, and the encapsulation type of its frames/records +-- Since Wireshark uses the file cursor position for future reading of this file, we also have to seek back to the beginning +-- so that our normal read() function works correctly. +local function read_open(file, capture) + dprint2("read_open() called") + + -- save current position to return later + local position = file:seek() + + if parse_file_header(file) then + + dprint2("read_open: success, file is for us") + + -- if the file is for us, we MUST set the file position cursor to + -- where we want the first call to read() function to get it the next time + -- for example if we checked a few records to be sure it's or type + -- but in this simple example we only verify the file header (24 bytes) + -- and we want the file position to remain after that header for our read() + -- call, so we don't change it back + --file:seek("set",position) + + -- these we can also set per record later during read operations + capture.time_precision = wtap_filetypes.TSPREC_USEC -- for microsecond precision + capture.encap = ENCAP_TYPE -- this was updated by parse_file_header() + capture.snapshot_length = SNAPLEN -- also updated by parse_file_header() + + return true + end + + dprint2("read_open: file not for us") + + -- if it's not for us, wireshark will reset the file position itself + -- but we might as well do it too, in case that behavior ever changes + file:seek("set",position) + + return false +end + +---------------------------------------- +-- Wireshark/tshark calls read() for each frame/record in the file +-- It passes in a File object and FrameInfo object to this function +-- It expects in return the file offset position the record starts at, +-- or nil/false if there's an error or end-of-file is reached. +-- The offset position is used later: wireshark remembers it and gives +-- it to seek_read() at various random times +local function read(file, frame) + dprint2("read() called") + + -- call our common reader function + local position = file:seek() + + if not read_common("read", file, frame) then + -- this isnt' actually an error, because it might just mean we reached end-of-file + -- so let's test for that (read(0) is a special case in Lua, see Lua docs) + if file:read(0) ~= nil then + dprint("read: failed to call read_common") + else + dprint2("read: reached end of file") + end + return false + end + + dprint2("read: succeess") + + -- return the position we got to (or nil if we hit EOF/error) + return position +end + +---------------------------------------- +-- Wireshark/tshark calls seek_read() for each frame/record in the file, at random times +-- It passes in to this function a File object, FrameInfo object, and the offset position number +-- It expects in return true for successful parsing, or nil/false if there's an error. +local function seek_read(file, frame, offset) + dprint2("seek_read() called") + + -- first move to the right position in the file + file:seek("set",offset) + + if not read_common("seek_read", file, frame) then + dprint("seek_read: failed to call read_common") + return false + end + + return true +end + +---------------------------------------- +-- Wireshark/tshark calls read_close() when it's closing the file completely +-- this is a good opportunity to clean up any state you may have created during +-- file reading. (in our case there's no real state) +local function read_close(file) + dprint2("read_close() called") + -- we don't really have to reset these, but just to show what you might do in this function... + VERSION_MAJOR = 2 + VERSION_MINOR = 4 + TIMEZONE = 0 + SIGFIGS = 0 + SNAPLEN = 0 + ENCAP_TYPE = wtap.UNKNOWN + return true +end + +---------------------------------------- +-- An often unused function, Wireshark calls this when the sequential walk-through is over +-- (i.e., no more calls to read(), only to seek_read()). +-- This gives you a chance to clean up any state you used during read() calls, but remember +-- that there will be calls to seek_read() after this (in Wireshark, though not Tshark) +local function seq_read_close(file) + dprint2("First pass of read() calls are over, but there may be seek_read() calls after this") + return true +end + +---------------------------------------- +-- ok, so let's create a FileHandler object +local fh = FileHandler.new("Lua-based PCAP reader", "lua_pcap", "A Lua-based file reader for PCAP-type files","rs") + +-- set above functions to the FileHandler +fh.read_open = read_open +fh.read = read +fh.seek_read = seek_read +fh.read_close = read_close +fh.seq_read_close = seq_read_close +fh.extensions = "pcap;cap" -- this is just a hint + +-- and finally, register the FileHandler! +register_filehandler(fh) + +dprint2("FileHandler registered") + +-------------------------------------------------------------------------------- +-- ok now for the boring stuff that actually does the work +-------------------------------------------------------------------------------- + +---------------------------------------- +-- in Lua, we have access to encapsulation types in the 'wtap_encaps' table, but +-- those numbers don't actually necessarily match the numbers in pcap files +-- for the encapsulation type, because the namespace got screwed up at some +-- point in the past (blame LBL NRG, not wireshark for that) +-- but I'm not going to create the full mapping of these two namespaces +-- instead we'll just use this smaller table to map them +-- these are taken from wiretap/pcap-common.c +local pcap2wtap = { + [0] = wtap_encaps.NULL, + [1] = wtap_encaps.ETHERNET, + [6] = wtap_encaps.TOKEN_RING, + [8] = wtap_encaps.SLIP, + [9] = wtap_encaps.PPP, + [101] = wtap_encaps.RAW_IP, + [105] = wtap_encaps.IEEE_802_11, + [140] = wtap_encaps.MTP2, + [141] = wtap_encaps.MTP3, + [143] = wtap_encaps.DOCSIS, + [147] = wtap_encaps.USER0, + [148] = wtap_encaps.USER1, + [149] = wtap_encaps.USER2, + [150] = wtap_encaps.USER3, + [151] = wtap_encaps.USER4, + [152] = wtap_encaps.USER5, + [153] = wtap_encaps.USER6, + [154] = wtap_encaps.USER7, + [155] = wtap_encaps.USER8, + [156] = wtap_encaps.USER9, + [157] = wtap_encaps.USER10, + [158] = wtap_encaps.USER11, + [159] = wtap_encaps.USER12, + [160] = wtap_encaps.USER13, + [161] = wtap_encaps.USER14, + [162] = wtap_encaps.USER15, + [186] = wtap_encaps.USB, + [187] = wtap_encaps.BLUETOOTH_H4, + [189] = wtap_encaps.USB_LINUX, + [195] = wtap_encaps.IEEE802_15_4, +} + +-- we can use the above to directly map very quickly +-- but to map it backwards we'll use this, because I'm lazy: +local function wtap2pcap(encap) + for k,v in pairs(pcap2wtap) do + if v == encap then + return k + end + end + return 0 +end + +---------------------------------------- +-- the pcap magic field: 0xA1B2C3D4, of both endianess +local MAGIC = 0xa1b2c3d4 +local SWAPPED_MAGIC = 0xd4c3b2a1 + +-- here are the "structs" we're going to parse, of the various records in a pcap file +-- these pattern string gets used in calls to Struct.unpack() +-- +-- we will prepend a '<' or '>' later, once we figure out what endian-ess the files are in +-- +-- a pcap file header struct +-- this is: magic, version_major, version_minor, timezone, sigfigs, snaplen, encap type +local FILE_HEADER = "I4 I2 I2 i4 I4 I4 I4" +local FILE_HDR_LEN = Struct.size(FILE_HEADER) + +-- a pcap record header struct +-- this is: time_sec, time_usec, capture_len, original_len +local REC_HEADER = "I4 I4 I4 I4" +local REC_HDR_LEN = Struct.size(REC_HEADER) +local NUM_REC_FIELDS = 4 + +-- these will hold the '<'/'>' prepended version of above +local file_header, rec_header + +-- snaplen/caplen can't be bigger than this +local WTAP_MAX_PACKET_SIZE = 65535 + +---------------------------------------- +-- internal functions declared previously +---------------------------------------- + +---------------------------------------- +-- used by read_open(), this parses the file header +parse_file_header = function(file) + dprint2("parse_file_header() called") + + -- by default, file:read() gets the next "string", meaning ending with a newline \n + -- but we want raw byte reads, so tell it how many bytes to read + local line = file:read(FILE_HDR_LEN) + + -- it's ok for us to not be able to read it, but we need to tell wireshark the + -- file's not for us, so return false + if not line then return false end + + dprint2("parse_file_header: got this line:\n'", Struct.tohex(line,false,":"), "'") + + -- let's peek at the magic int32, assuming it's little-endian + local magic = Struct.unpack("<I4", line) + + if magic == MAGIC then + dprint2("file is little-endian") + file_header = "<" .. FILE_HEADER + rec_header = "<" .. REC_HEADER + elseif magic == SWAPPED_MAGIC then + dprint2("file is big-endian") + file_header = ">" .. FILE_HEADER + rec_header = ">" .. REC_HEADER + else + dprint("magic was:",magic," so not a pcap file") + return false + end + + local nettype + + magic, VERSION_MAJOR, VERSION_MINOR, TIMEZONE, SIGFIGS, SNAPLEN, nettype = Struct.unpack(file_header, line) + + if not magic then + dprint("parse_file_header: failed to unpack header struct") + return false + end + + dprint("parse_file_header: got magic=",magic, ", major version=",VERSION_MAJOR, ", minor=",VERSION_MINOR, + ", timezone=",TIMEZONE, ", sigfigs=",SIGFIGS, "snaplen=",SNAPLEN, ", nettype =",nettype) + + -- wireshark only supports version 2.0 and later + if VERSION_MAJOR < 2 then + dprint("got version =",VERSION_MAJOR,"but only version 2 or greater supported") + return false + end + + -- convert pcap file interface type to wtap number type + ENCAP_TYPE = pcap2wtap[nettype] + if not ENCAP_TYPE then + dprint("file nettype",nettype,"couldn't be mapped to wireshark wtap type") + return false + end + + + if SNAPLEN > WTAP_MAX_PACKET_SIZE then + SNAPLEN = WTAP_MAX_PACKET_SIZE + end + + --ok, it's a pcap file + dprint2("parse_file_header: success") + return true +end + +---------------------------------------- +-- this is used by both read() and seek_read() +-- the calling function to this should have already set the file position correctly +read_common = function(funcname, file, frame) + dprint2(funcname,": read_common() called") + + -- first parse the record header, which will set the FrameInfo fields + if not parse_rec_header(funcname, file, frame) then + dprint2(funcname, ": read_common: hit end of file or error") + return false + end + + frame.encap = ENCAP_TYPE + + -- now we need to get the packet bytes from the file record into the frame... + -- we *could* read them into a string using file:read(numbytes), and then + -- set them to frame.data so that wireshark gets it... + -- but that would mean the packet's string would be copied into Lua + -- and then sent right back into wireshark, which is gonna slow things + -- down; instead FrameInfo has a read_data() method, which makes + -- wireshark read directly from the file into the frame buffer, so we use that + if not frame:read_data(file, frame.captured_length) then + dprint(funcname, ": read_common: failed to read data from file into buffer") + return false + end + + return true +end + +---------------------------------------- +-- the function to parse individual records +parse_rec_header = function(funcname, file, frame) + dprint2(funcname,": parse_rec_header() called") + + local line = file:read(REC_HDR_LEN) + + -- it's ok for us to not be able to read it, if it's end of file + if not line then return false end + + -- this is: time_sec, time_usec, capture_len, original_len + local fields = { Struct.unpack(rec_header, line) } + + -- sanity check; also note that Struct.unpack() returns the fields plus + -- a number of where in the line it stopped reading (ie, the end in this case) + -- so we got back number of fields + 1 + if #fields ~= NUM_REC_FIELDS + 1 then + dprint(funcname, ": parse_rec_header: failed to read the record header") + return nil + end + + -- we could just do this: + --frame.time = fields[1] + (fields[2] / 1000000) + -- but Lua numbers are doubles, which lose precision in the fractional part + -- so we use a NSTime() object instead; remember though that an NSTime takes + -- nanoseconds for its second arg, and pcap's are only microseconds, so *1000 + frame.time = NSTime(fields[1], fields[2]*1000) + + -- sanity check, verify captured length isn't more than original length + if fields[3] > fields[4] then + dprint("captured length of",fields[3],"is bigger than original length of",fields[4]) + -- swap them + local caplen = fields[3] + fields[3] = fields[4] + fields[4] = caplen + end + + if fields[3] > WTAP_MAX_PACKET_SIZE then + dprint("Got a captured_length of",fields[3],"which is too big") + return nil + end + + frame.captured_length = fields[3] + frame.original_length = fields[4] + + frame.flags = wtap_presence_flags.TS + wtap_presence_flags.CAP_LEN -- for timestamp|cap_len + + return true +end + + + +-------------------------------------------------------------------------------- +-- file writer handling functions for Wireshark to use +-------------------------------------------------------------------------------- + +-- file encaps we can handle writing +local canwrite = { + [ wtap_encaps.NULL ] = true, + [ wtap_encaps.ETHERNET ] = true, + [ wtap_encaps.PPP ] = true, + [ wtap_encaps.RAW_IP ] = true, + [ wtap_encaps.IEEE_802_11 ] = true, + [ wtap_encaps.MTP2 ] = true, + [ wtap_encaps.MTP3 ] = true, + -- etc., etc. +} + +-- we can't reuse the variables we used in the reader, because this script might be sued to both +-- open a file for reading and write it out, at the same time, so we prepend 'W_' for the writer's +-- versions. Normally I'd put this type of stuff in a class table and just create a new instance, +-- but I didn't want to confuse people with Lua class models in this script +local W_VERSION_MAJOR = 2 +local W_VERSION_MINOR = 4 +local W_TIMEZONE = 0 +local W_SIGFIGS = 0 +local W_SNAPLEN = 0 +local W_ENCAP_TYPE = wtap.UNKNOWN +-- write out things in little-endian order +local w_file_header = "<" .. FILE_HEADER +local w_rec_header = "<" .. REC_HEADER +local TSPRECISION = wtap_filetypes.TSPREC_USEC + +---------------------------------------- +-- The can_write_encap() function is called by Wireshark when it wants to write out a file, +-- and needs to see if this file writer can handle the packet types in the window. +-- We need to return true if we can handle it, else false +local function can_write_encap(encap) + dprint2("can_write_encap() called with encap=",encap) + return canwrite[encap] or false +end + +local function write_open(file, capture) + dprint2("write_open() called") + + -- write out file header + local hdr = Struct.pack(w_file_header, + MAGIC, W_VERSION_MAJOR, W_VERSION_MINOR, + W_TIMEZONE, W_SIGFIGS, capture.snapshot_length, wtap2pcap(capture.encap)) + + if not hdr then + dprint("write_open: error generating file header") + return false + end + + dprint2("write_open generating:",Struct.tohex(hdr)) + + if not file:write(hdr) then + dprint("write_open: error writing file header to file") + return false + end + + return true +end + +local function write(file, frame) + dprint2("write() called") + + -- write out record header: time_sec, time_usec, capture_len, original_len + + -- first get times + local nstime = frame.time + + -- pcap format is in usecs + local nsecs = nstime.nsecs / 1000 + + local hdr = Struct.pack(w_rec_header, nstime.secs, nsecs, frame.captured_length, frame.original_length) + + if not hdr then + dprint("write_open: error generating record header") + return false + end + + if not file:write(hdr) then + dprint("write_open: error writing record header to file") + return false + end + + -- we could write the packet data the same way, by getting frame.data and writing it out + -- but we can avoid copying those bytes into Lua by using the write_data() function + if not frame:write_data(file) then + dprint("write_open: error writing record data to file") + return false + end + + return true +end + +local function write_close(file) + dprint2("write_close() called") + dprint2("Good night, and good luck") + return true +end + +-- ok, so let's create another FileHandler object +local fh2 = FileHandler.new("Lua-based PCAP writer", "lua_pcap2", "A Lua-based file writer for PCAP-type files","wms") + +-- set above functions to the FileHandler +fh2.can_write_encap = can_write_encap +fh2.write_open = write_open +fh2.write = write +fh2.write_close = write_close +fh2.extensions = "pcap;cap" -- this is just a hint + +-- and finally, register the FileHandler! +register_filehandler(fh2) + +dprint2("Second FileHandler registered") diff --git a/test/suite-wslua.sh b/test/suite-wslua.sh index fa682b08d3..0b8fc4e339 100755 --- a/test/suite-wslua.sh +++ b/test/suite-wslua.sh @@ -66,6 +66,103 @@ wslua_step_field_test() { fi } +wslua_step_file_test() { + if [ $HAVE_LUA -ne 0 ]; then + test_step_skipped + return + fi + + # First run tshark with the pcap_file_reader script. + $TSHARK -r $CAPTURE_DIR/dhcp.pcap -X lua_script:$TESTS_DIR/lua/pcap_file.lua > testin.txt 2>&1 + $TSHARK -r $CAPTURE_DIR/wpa-Induction.pcap.gz -X lua_script:$TESTS_DIR/lua/pcap_file.lua >> testin.txt 2>&1 + RETURNVALUE=$? + if [ ! $RETURNVALUE -eq $EXIT_OK ]; then + echo + cat ./testin.txt + test_step_failed "exit status of $DUT: $RETURNVALUE" + return + fi + + # then run tshark again without the script + $TSHARK -r $CAPTURE_DIR/dhcp.pcap > testout.txt 2>&1 + $TSHARK -r $CAPTURE_DIR/wpa-Induction.pcap.gz >> testout.txt 2>&1 + RETURNVALUE=$? + if [ ! $RETURNVALUE -eq $EXIT_OK ]; then + echo + cat ./testout.txt + test_step_failed "exit status of $DUT: $RETURNVALUE" + return + fi + + # now compare the two files - they should be identical + if diff -q ./testin.txt ./testout.txt; then + rm ./testin.txt + else + echo + cat ./testin.txt + cat ./testout.txt + test_step_failed "reading the pcap file with Lua did not match internal" + fi + + # Now generate a new capture file using the Lua writer. + $TSHARK -r $CAPTURE_DIR/dhcp.pcap -X lua_script:$TESTS_DIR/lua/pcap_file.lua -w testin.txt -F lua_pcap2 > testout.txt 2>&1 + RETURNVALUE=$? + if [ ! $RETURNVALUE -eq $EXIT_OK ]; then + echo + cat ./testout.txt + test_step_failed "exit status of $DUT: $RETURNVALUE" + return + fi + + # now compare the two files - they should be identical + if diff -q $CAPTURE_DIR/dhcp.pcap ./testin.txt; then + rm ./testin.txt + else + echo + cat ./testout.txt + test_step_failed "writing the pcap out as Lua did not match dhcp.cap" + fi + + # Now read an acme sipmsg.log using the acme Lua reader, writing it out as pcapng. + $TSHARK -r $CAPTURE_DIR/sipmsg.log -X lua_script:$TESTS_DIR/lua/acme_file.lua -w testin.txt -F pcapng > testout.txt 2>&1 + RETURNVALUE=$? + if [ ! $RETURNVALUE -eq $EXIT_OK ]; then + echo + cat ./testout.txt + test_step_failed "exit status of $DUT: $RETURNVALUE" + return + fi + + # testin.txt is now a pcapng, read it out using -V verbose into testout.txt + $TSHARK -r ./testin.txt -V > testout.txt 2>&1 + RETURNVALUE=$? + if [ ! $RETURNVALUE -eq $EXIT_OK ]; then + echo + cat ./testout.txt + test_step_failed "exit status of $DUT: $RETURNVALUE" + return + fi + + # now readout sip.pcapng into testin.txt using -V verbose + $TSHARK -r $CAPTURE_DIR/sip.pcapng -V > testin.txt 2>&1 + RETURNVALUE=$? + if [ ! $RETURNVALUE -eq $EXIT_OK ]; then + echo + cat ./testin.txt + test_step_failed "exit status of $DUT: $RETURNVALUE" + return + fi + + # now compare testin and testout - they should be identical + if diff -q ./testout.txt ./testin.txt; then + test_step_ok + else + echo + cat ./testout.txt + test_step_failed "writing the acme sipmsg.log out as pcapng did not match sip.pcapng" + fi +} + wslua_step_listener_test() { if [ $HAVE_LUA -ne 0 ]; then test_step_skipped @@ -260,6 +357,7 @@ wslua_suite() { test_step_set_post wslua_cleanup_step test_step_add "wslua dissector" wslua_step_dissector_test test_step_add "wslua field/fieldinfo" wslua_step_field_test + test_step_add "wslua file" wslua_step_file_test test_step_add "wslua globals" wslua_step_globals_test test_step_add "wslua gregex" wslua_step_gregex_test test_step_add "wslua int64" wslua_step_int64_test diff --git a/wiretap/file_access.c b/wiretap/file_access.c index 1be8b35a72..d641aaaea1 100644 --- a/wiretap/file_access.c +++ b/wiretap/file_access.c @@ -300,39 +300,39 @@ GSList *wtap_get_all_file_extensions_list(void) */ static struct open_info open_info_base[] = { - { "Pcap", OPEN_INFO_MAGIC, libpcap_open, "pcap" }, - { "PcapNG", OPEN_INFO_MAGIC, pcapng_open, "pcapng"}, - { "NgSniffer", OPEN_INFO_MAGIC, ngsniffer_open, NULL }, - { "Snoop", OPEN_INFO_MAGIC, snoop_open, NULL }, - { "IP Trace", OPEN_INFO_MAGIC, iptrace_open, NULL }, - { "Netmon", OPEN_INFO_MAGIC, netmon_open, NULL }, - { "Netxray", OPEN_INFO_MAGIC, netxray_open, NULL }, - { "Radcom", OPEN_INFO_MAGIC, radcom_open, NULL }, - { "Nettl", OPEN_INFO_MAGIC, nettl_open, NULL }, - { "Visual", OPEN_INFO_MAGIC, visual_open, NULL }, - { "5 Views", OPEN_INFO_MAGIC, _5views_open, NULL }, - { "Network Instruments", OPEN_INFO_MAGIC, network_instruments_open, NULL }, - { "Peek Tagged", OPEN_INFO_MAGIC, peektagged_open, NULL }, - { "DBS Etherwatch", OPEN_INFO_MAGIC, dbs_etherwatch_open, NULL }, - { "K12", OPEN_INFO_MAGIC, k12_open, NULL }, - { "Catapult DCT 2000", OPEN_INFO_MAGIC, catapult_dct2000_open, NULL }, - { "Aethra", OPEN_INFO_MAGIC, aethra_open, NULL }, - { "BTSNOOP", OPEN_INFO_MAGIC, btsnoop_open, "log" }, - { "EYESDN", OPEN_INFO_MAGIC, eyesdn_open, NULL }, - { "TNEF", OPEN_INFO_MAGIC, tnef_open, NULL }, - { "MIME Files with Magic Bytes", OPEN_INFO_MAGIC, mime_file_open, NULL }, - { "Lanalyzer", OPEN_INFO_HEURISTIC, lanalyzer_open, "tr1" }, + { "Pcap", OPEN_INFO_MAGIC, libpcap_open, "pcap", NULL }, + { "PcapNG", OPEN_INFO_MAGIC, pcapng_open, "pcapng", NULL }, + { "NgSniffer", OPEN_INFO_MAGIC, ngsniffer_open, NULL, NULL }, + { "Snoop", OPEN_INFO_MAGIC, snoop_open, NULL, NULL }, + { "IP Trace", OPEN_INFO_MAGIC, iptrace_open, NULL, NULL }, + { "Netmon", OPEN_INFO_MAGIC, netmon_open, NULL, NULL }, + { "Netxray", OPEN_INFO_MAGIC, netxray_open, NULL, NULL }, + { "Radcom", OPEN_INFO_MAGIC, radcom_open, NULL, NULL }, + { "Nettl", OPEN_INFO_MAGIC, nettl_open, NULL, NULL }, + { "Visual", OPEN_INFO_MAGIC, visual_open, NULL, NULL }, + { "5 Views", OPEN_INFO_MAGIC, _5views_open, NULL, NULL }, + { "Network Instruments", OPEN_INFO_MAGIC, network_instruments_open, NULL, NULL }, + { "Peek Tagged", OPEN_INFO_MAGIC, peektagged_open, NULL, NULL }, + { "DBS Etherwatch", OPEN_INFO_MAGIC, dbs_etherwatch_open, NULL, NULL }, + { "K12", OPEN_INFO_MAGIC, k12_open, NULL, NULL }, + { "Catapult DCT 2000", OPEN_INFO_MAGIC, catapult_dct2000_open, NULL, NULL }, + { "Aethra", OPEN_INFO_MAGIC, aethra_open, NULL, NULL }, + { "BTSNOOP", OPEN_INFO_MAGIC, btsnoop_open, "log", NULL }, + { "EYESDN", OPEN_INFO_MAGIC, eyesdn_open, NULL, NULL }, + { "TNEF", OPEN_INFO_MAGIC, tnef_open, NULL, NULL }, + { "MIME Files with Magic Bytes", OPEN_INFO_MAGIC, mime_file_open, NULL, NULL }, + { "Lanalyzer", OPEN_INFO_HEURISTIC, lanalyzer_open, "tr1", NULL }, /* * PacketLogger must come before MPEG, because its files * are sometimes grabbed by mpeg_open. */ - { "Packet Logger", OPEN_INFO_HEURISTIC, packetlogger_open, "pklg" }, + { "Packet Logger", OPEN_INFO_HEURISTIC, packetlogger_open, "pklg", NULL }, /* Some MPEG files have magic numbers, others just have heuristics. */ - { "Mpeg", OPEN_INFO_HEURISTIC, mpeg_open, "mpg;mp3" }, - { "DCT3 Trace", OPEN_INFO_HEURISTIC, dct3trace_open, "xml" }, - { "Daintree SNA", OPEN_INFO_HEURISTIC, daintree_sna_open, "dcf" }, - { "Stanag 4607", OPEN_INFO_HEURISTIC, stanag4607_open, NULL }, - { "BER", OPEN_INFO_HEURISTIC, ber_open, NULL }, + { "Mpeg", OPEN_INFO_HEURISTIC, mpeg_open, "mpg;mp3", NULL }, + { "DCT3 Trace", OPEN_INFO_HEURISTIC, dct3trace_open, "xml", NULL }, + { "Daintree SNA", OPEN_INFO_HEURISTIC, daintree_sna_open, "dcf", NULL }, + { "Stanag 4607", OPEN_INFO_HEURISTIC, stanag4607_open, NULL, NULL }, + { "BER", OPEN_INFO_HEURISTIC, ber_open, NULL, NULL }, /* I put NetScreen *before* erf, because there were some * false positives with my test-files (Sake Blok, July 2007) * @@ -345,34 +345,59 @@ static struct open_info open_info_base[] = { * because there were some cases where files of those types were * misidentified as vwr files (Guy Harris, December 2013) */ - { "Netscreen", OPEN_INFO_HEURISTIC, netscreen_open, "txt" }, - { "ERF", OPEN_INFO_HEURISTIC, erf_open, "erf" }, - { "IPfix", OPEN_INFO_HEURISTIC, ipfix_open, "pfx;ipfix" }, - { "K12 Text", OPEN_INFO_HEURISTIC, k12text_open, "txt" }, - { "Peek Classic", OPEN_INFO_HEURISTIC, peekclassic_open, "pkt;tpc;apc;wpz" }, - { "PPP Dump", OPEN_INFO_HEURISTIC, pppdump_open, NULL }, - { "iSeries", OPEN_INFO_HEURISTIC, iseries_open, "txt" }, - { "i4btrace", OPEN_INFO_HEURISTIC, i4btrace_open, NULL }, - { "Mp2t", OPEN_INFO_HEURISTIC, mp2t_open, "ts;mpg" }, - { "Csids", OPEN_INFO_HEURISTIC, csids_open, NULL }, - { "VMS", OPEN_INFO_HEURISTIC, vms_open, "txt" }, - { "Cosine", OPEN_INFO_HEURISTIC, cosine_open, "txt" }, - { "Hcidump", OPEN_INFO_HEURISTIC, hcidump_open, NULL }, - { "Commview", OPEN_INFO_HEURISTIC, commview_open, "ncf" }, - { "Nstrace", OPEN_INFO_HEURISTIC, nstrace_open, "txt" }, + { "Netscreen", OPEN_INFO_HEURISTIC, netscreen_open, "txt", NULL }, + { "ERF", OPEN_INFO_HEURISTIC, erf_open, "erf", NULL }, + { "IPfix", OPEN_INFO_HEURISTIC, ipfix_open, "pfx;ipfix",NULL }, + { "K12 Text", OPEN_INFO_HEURISTIC, k12text_open, "txt", NULL }, + { "Peek Classic", OPEN_INFO_HEURISTIC, peekclassic_open, "pkt;tpc;apc;wpz", NULL }, + { "PPP Dump", OPEN_INFO_HEURISTIC, pppdump_open, NULL, NULL }, + { "iSeries", OPEN_INFO_HEURISTIC, iseries_open, "txt", NULL }, + { "i4btrace", OPEN_INFO_HEURISTIC, i4btrace_open, NULL, NULL }, + { "Mp2t", OPEN_INFO_HEURISTIC, mp2t_open, "ts;mpg", NULL }, + { "Csids", OPEN_INFO_HEURISTIC, csids_open, NULL, NULL }, + { "VMS", OPEN_INFO_HEURISTIC, vms_open, "txt", NULL }, + { "Cosine", OPEN_INFO_HEURISTIC, cosine_open, "txt", NULL }, + { "Hcidump", OPEN_INFO_HEURISTIC, hcidump_open, NULL, NULL }, + { "Commview", OPEN_INFO_HEURISTIC, commview_open, "ncf", NULL }, + { "Nstrace", OPEN_INFO_HEURISTIC, nstrace_open, "txt", NULL }, /* ASCII trace files from Telnet sessions. */ - { "Ascend", OPEN_INFO_HEURISTIC, ascend_open, "txt" }, - { "Toshiba", OPEN_INFO_HEURISTIC, toshiba_open, "txt" }, + { "Ascend", OPEN_INFO_HEURISTIC, ascend_open, "txt", NULL }, + { "Toshiba", OPEN_INFO_HEURISTIC, toshiba_open, "txt", NULL }, /* Extremely weak heuristics - put them at the end. */ - { "VWR", OPEN_INFO_HEURISTIC, vwr_open, "vwr" }, - { "Camins", OPEN_INFO_HEURISTIC, camins_open, "camins" }, - { NULL, 0, NULL, NULL} + { "VWR", OPEN_INFO_HEURISTIC, vwr_open, "vwr", NULL }, + { "Camins", OPEN_INFO_HEURISTIC, camins_open, "camins", NULL }, + { NULL, 0, NULL, NULL, NULL } }; + +/* this is only used to build the dynamic array on load, do NOT use this + * for anything else, because the size of the actual array will change if + * Lua scripts register a new file reader. + */ #define N_OPEN_INFO_ROUTINES ((sizeof open_info_base / sizeof open_info_base[0])) +static GArray *open_info_arr = NULL; + +/* this always points to the top of the created array */ struct open_info *open_routines = NULL; -static GArray *open_info_arr = NULL; +/* this points to the first OPEN_INFO_HEURISTIC type in the array */ +static guint heuristic_open_routine_idx = 0; + +static void set_heuristic_routine(void) { + guint i; + g_assert(open_info_arr != NULL); + + for (i = 0; i < open_info_arr->len - 1; i++) { + if (open_routines[i].type == OPEN_INFO_HEURISTIC) { + heuristic_open_routine_idx = i; + break; + } + /* sanity check */ + g_assert(open_routines[i].type == OPEN_INFO_MAGIC); + } + + g_assert(heuristic_open_routine_idx > 0); +} void init_open_routines(void) { @@ -382,15 +407,87 @@ void init_open_routines(void) { g_array_append_vals(open_info_arr, open_info_base, N_OPEN_INFO_ROUTINES); - open_routines = (struct open_info *) open_info_arr->data; + open_routines = (struct open_info *)(void*) open_info_arr->data; + set_heuristic_routine(); +} + +/* Registers a new file reader - currently only called by wslua code for Lua readers. + * If first_routine is true, it's added before other readers of its type (magic or heuristic). + * Also, it checks for an existing reader of the same name and errors if it finds one; if + * you want to handle that condition more gracefully, call wtap_has_open_info() first. + */ +void wtap_register_open_info(const struct open_info *oi, const gboolean first_routine) { + init_open_routines(); + + if (!oi || !oi->name) { + g_error("No open_info name given to register"); + return; + } + + /* verify name doesn't already exist */ + if (wtap_has_open_info(oi->name)) { + g_error("Name given to register_open_info already exists"); + return; + } + + /* if it's magic and first, prepend it; if it's heuristic and not first, + append it; if it's anything else, stick it in the middle */ + if (first_routine && oi->type == OPEN_INFO_MAGIC) { + g_array_prepend_val(open_info_arr, *oi); + } else if (!first_routine && oi->type == OPEN_INFO_HEURISTIC) { + g_array_append_val(open_info_arr, *oi); + } else { + g_array_insert_val(open_info_arr, heuristic_open_routine_idx, *oi); + } + + open_routines = (struct open_info *)(void*) open_info_arr->data; + set_heuristic_routine(); } -void wtap_register_open_info(const struct open_info *oi) { +/* De-registers a file reader by removign it from the GArray based on its name. + * This function must NOT be called during wtap_open_offline(), since it changes the array. + * Note: this function will error if it doesn't find the given name; if you want to handle + * that condition more gracefully, call wtap_has_open_info() first. + */ +void wtap_deregister_open_info(const gchar *name) { + guint i; init_open_routines(); - g_array_append_val(open_info_arr, oi); + if (!name) { + g_error("Missing open_info name to de-register"); + return; + } + + for (i = 0; i < open_info_arr->len - 1; i++) { + if (open_routines[i].name && strcmp(open_routines[i].name, name) == 0) { + open_info_arr = g_array_remove_index(open_info_arr, i); + set_heuristic_routine(); + return; + } + } - open_routines = (struct open_info *) open_info_arr->data; + g_error("deregister_open_info: name not found"); +} + +/* Determines if a open routine short name already exists + */ +gboolean wtap_has_open_info(const gchar *name) { + guint i; + init_open_routines(); + + if (!name) { + g_error("No name given to wtap_has_open_info!"); + return FALSE; + } + + + for (i = 0; i < open_info_arr->len - 1; i++) { + if (open_routines[i].name && strcmp(open_routines[i].name, name) == 0) { + return TRUE; + } + } + + return FALSE; } /* @@ -527,6 +624,11 @@ static char *get_file_extension(const char *pathname) return extensionp; } +/* TODO: this is called every time a file is checked for matching heuristic, + * which means just clicking on a file in the open dialog may call this thing + * twice... for *each* open_routine. That's silly, since this info never changes. + * It would be better to create this list in the lookup array on initialization. + */ static gboolean heuristic_uses_extension(unsigned int i, const char *extension) { gchar **extensions_set, **extensionp; @@ -686,6 +788,7 @@ wtap* wtap_open_offline(const char *filename, unsigned int type, int *err, char wth->subtype_close = NULL; wth->tsprecision = WTAP_FILE_TSPREC_USEC; wth->priv = NULL; + wth->wslua_data = NULL; if (wth->random_fh) { wth->fast_seek = g_ptr_array_new(); @@ -694,7 +797,8 @@ wtap* wtap_open_offline(const char *filename, unsigned int type, int *err, char file_set_random_access(wth->random_fh, TRUE, wth->fast_seek); } - if (type != 0 && type <= open_info_arr->len + 1) { + /* 'type' is 1 greater than the array index */ + if (type != 0 && type <= open_info_arr->len) { int result; if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) { @@ -703,6 +807,12 @@ wtap* wtap_open_offline(const char *filename, unsigned int type, int *err, char return NULL; } + /* Set wth with wslua data if any - this is how we pass the data + * to the file reader, kinda like the priv member but not free'd later. + * It's ok for this to copy a NULL. + */ + wth->wslua_data = open_routines[type - 1].wslua_data; + result = (*open_routines[type - 1].open_routine)(wth, err, err_info); switch (result) { @@ -730,14 +840,18 @@ wtap* wtap_open_offline(const char *filename, unsigned int type, int *err, char to start reading at the beginning. Initialize the data offset while we're at it. */ - if (open_routines[i].type != OPEN_INFO_MAGIC) continue; - if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) { /* I/O error - give up */ wtap_close(wth); return NULL; } + /* Set wth with wslua data if any - this is how we pass the data + * to the file reader, kinda like the priv member but not free'd later. + * It's ok for this to copy a NULL. + */ + wth->wslua_data = open_routines[i].wslua_data; + switch ((*open_routines[i].open_routine)(wth, err, err_info)) { case -1: @@ -755,14 +869,13 @@ wtap* wtap_open_offline(const char *filename, unsigned int type, int *err, char } } + /* Does this file's name have an extension? */ extension = get_file_extension(filename); if (extension != NULL) { /* Yes - try the heuristic types that use that extension first. */ - for (i = 0; i < open_info_arr->len - 1; i++) { - if (open_routines[i].type != OPEN_INFO_HEURISTIC) continue; + for (i = heuristic_open_routine_idx; i < open_info_arr->len - 1; i++) { /* Does this type use that extension? */ - if (heuristic_uses_extension(i, extension)) { /* Yes. */ if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) { @@ -772,6 +885,11 @@ wtap* wtap_open_offline(const char *filename, unsigned int type, int *err, char return NULL; } + /* Set wth with wslua data if any - this is how we pass the data + * to the file reader, kind of like priv but not free'd later. + */ + wth->wslua_data = open_routines[i].wslua_data; + switch ((*open_routines[i].open_routine)(wth, err, err_info)) { @@ -794,8 +912,7 @@ wtap* wtap_open_offline(const char *filename, unsigned int type, int *err, char } /* Now try the ones that don't use it. */ - for (i = 0; i < open_info_arr->len - 1; i++) { - if (open_routines[i].type != OPEN_INFO_HEURISTIC) continue; + for (i = heuristic_open_routine_idx; i < open_info_arr->len - 1; i++) { /* Does this type use that extension? */ if (!heuristic_uses_extension(i, extension)) { /* No. */ @@ -806,6 +923,11 @@ wtap* wtap_open_offline(const char *filename, unsigned int type, int *err, char return NULL; } + /* Set wth with wslua data if any - this is how we pass the data + * to the file reader, kind of like priv but not free'd later. + */ + wth->wslua_data = open_routines[i].wslua_data; + switch ((*open_routines[i].open_routine)(wth, err, err_info)) { @@ -829,8 +951,7 @@ wtap* wtap_open_offline(const char *filename, unsigned int type, int *err, char g_free(extension); } else { /* No - try all the heuristics types in order. */ - for (i = 0; i < open_info_arr->len - 1; i++) { - if (open_routines[i].type != OPEN_INFO_HEURISTIC) continue; + for (i = heuristic_open_routine_idx; i < open_info_arr->len - 1; i++) { if (file_seek(wth->fh, 0, SEEK_SET, err) == -1) { /* I/O error - give up */ @@ -838,6 +959,11 @@ wtap* wtap_open_offline(const char *filename, unsigned int type, int *err, char return NULL; } + /* Set wth with wslua data if any - this is how we pass the data + * to the file reader, kind of like priv but not free'd later. + */ + wth->wslua_data = open_routines[i].wslua_data; + switch ((*open_routines[i].open_routine)(wth, err, err_info)) { case -1: @@ -963,338 +1089,338 @@ static const struct file_type_subtype_info dump_open_table_base[] = { /* WTAP_FILE_TYPE_SUBTYPE_UNKNOWN (only used internally for initialization) */ { NULL, NULL, NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PCAP */ /* Gianluca Varenni suggests that we add "deprecated" to the description. */ { "Wireshark/tcpdump/... - pcap", "pcap", "pcap", "cap;dmp", FALSE, FALSE, 0, - libpcap_dump_can_write_encap, libpcap_dump_open }, + libpcap_dump_can_write_encap, libpcap_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PCAPNG */ { "Wireshark/... - pcapng", "pcapng", "pcapng", "ntar", FALSE, TRUE, WTAP_COMMENT_PER_SECTION|WTAP_COMMENT_PER_INTERFACE|WTAP_COMMENT_PER_PACKET, - pcapng_dump_can_write_encap, pcapng_dump_open }, + pcapng_dump_can_write_encap, pcapng_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PCAP_NSEC */ { "Wireshark - nanosecond libpcap", "nseclibpcap", "pcap", "cap;dmp", FALSE, FALSE, 0, - libpcap_dump_can_write_encap, libpcap_dump_open }, + libpcap_dump_can_write_encap, libpcap_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PCAP_AIX */ { "AIX tcpdump - libpcap", "aixlibpcap", "pcap", "cap;dmp", FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PCAP_SS991029 */ { "Modified tcpdump - libpcap", "modlibpcap", "pcap", "cap;dmp", FALSE, FALSE, 0, - libpcap_dump_can_write_encap, libpcap_dump_open }, + libpcap_dump_can_write_encap, libpcap_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PCAP_NOKIA */ { "Nokia tcpdump - libpcap ", "nokialibpcap", "pcap", "cap;dmp", FALSE, FALSE, 0, - libpcap_dump_can_write_encap, libpcap_dump_open }, + libpcap_dump_can_write_encap, libpcap_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PCAP_SS990417 */ { "RedHat 6.1 tcpdump - libpcap", "rh6_1libpcap", "pcap", "cap;dmp", FALSE, FALSE, 0, - libpcap_dump_can_write_encap, libpcap_dump_open }, + libpcap_dump_can_write_encap, libpcap_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PCAP_SS990915 */ { "SuSE 6.3 tcpdump - libpcap", "suse6_3libpcap", "pcap", "cap;dmp", FALSE, FALSE, 0, - libpcap_dump_can_write_encap, libpcap_dump_open }, + libpcap_dump_can_write_encap, libpcap_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_5VIEWS */ { "InfoVista 5View capture", "5views", "5vw", NULL, TRUE, FALSE, 0, - _5views_dump_can_write_encap, _5views_dump_open }, + _5views_dump_can_write_encap, _5views_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_IPTRACE_1_0 */ { "AIX iptrace 1.0", "iptrace_1", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_IPTRACE_2_0 */ { "AIX iptrace 2.0", "iptrace_2", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_BER */ { "ASN.1 Basic Encoding Rules", "ber", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_HCIDUMP */ { "Bluetooth HCI dump", "hcidump", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_CATAPULT_DCT2000 */ { "Catapult DCT2000 trace (.out format)", "dct2000", "out", NULL, FALSE, FALSE, 0, - catapult_dct2000_dump_can_write_encap, catapult_dct2000_dump_open }, + catapult_dct2000_dump_can_write_encap, catapult_dct2000_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETXRAY_OLD */ { "Cinco Networks NetXRay 1.x", "netxray1", "cap", NULL, TRUE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETXRAY_1_0 */ { "Cinco Networks NetXRay 2.0 or later", "netxray2", "cap", NULL, TRUE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_COSINE */ { "CoSine IPSX L2 capture", "cosine", "txt", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_CSIDS */ { "CSIDS IPLog", "csids", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_DBS_ETHERWATCH */ { "DBS Etherwatch (VMS)", "etherwatch", "txt", NULL, FALSE, FALSE, 0, - NULL, NULL}, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_ERF */ { "Endace ERF capture", "erf", "erf", NULL, FALSE, FALSE, 0, - erf_dump_can_write_encap, erf_dump_open }, + erf_dump_can_write_encap, erf_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_EYESDN */ { "EyeSDN USB S0/E1 ISDN trace format", "eyesdn", "trc", NULL, FALSE, FALSE, 0, - eyesdn_dump_can_write_encap, eyesdn_dump_open }, + eyesdn_dump_can_write_encap, eyesdn_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETTL */ { "HP-UX nettl trace", "nettl", "trc0", "trc1", FALSE, FALSE, 0, - nettl_dump_can_write_encap, nettl_dump_open }, + nettl_dump_can_write_encap, nettl_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_ISERIES */ { "IBM iSeries comm. trace (ASCII)", "iseries_ascii", "txt", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_ISERIES_UNICODE */ { "IBM iSeries comm. trace (UNICODE)", "iseries_unicode", "txt", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_I4BTRACE */ { "I4B ISDN trace", "i4btrace", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_ASCEND */ { "Lucent/Ascend access server trace", "ascend", "txt", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETMON_1_x */ { "Microsoft NetMon 1.x", "netmon1", "cap", NULL, TRUE, FALSE, 0, - netmon_dump_can_write_encap_1_x, netmon_dump_open }, + netmon_dump_can_write_encap_1_x, netmon_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETMON_2_x */ { "Microsoft NetMon 2.x", "netmon2", "cap", NULL, TRUE, FALSE, 0, - netmon_dump_can_write_encap_2_x, netmon_dump_open }, + netmon_dump_can_write_encap_2_x, netmon_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NGSNIFFER_UNCOMPRESSED */ { "Sniffer (DOS)", "ngsniffer", "cap", "enc;trc;fdc;syc", FALSE, FALSE, 0, - ngsniffer_dump_can_write_encap, ngsniffer_dump_open }, + ngsniffer_dump_can_write_encap, ngsniffer_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NGSNIFFER_COMPRESSED */ { "Sniffer (DOS), compressed", "ngsniffer_comp", "cap", "enc;trc;fdc;syc", FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETXRAY_1_1 */ { "NetXray, Sniffer (Windows) 1.1", "ngwsniffer_1_1", "cap", NULL, TRUE, FALSE, 0, - netxray_dump_can_write_encap_1_1, netxray_dump_open_1_1 }, + netxray_dump_can_write_encap_1_1, netxray_dump_open_1_1, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETXRAY_2_00x */ { "Sniffer (Windows) 2.00x", "ngwsniffer_2_0", "cap", "caz", TRUE, FALSE, 0, - netxray_dump_can_write_encap_2_0, netxray_dump_open_2_0 }, + netxray_dump_can_write_encap_2_0, netxray_dump_open_2_0, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETWORK_INSTRUMENTS */ { "Network Instruments Observer", "niobserver", "bfr", NULL, FALSE, FALSE, 0, - network_instruments_dump_can_write_encap, network_instruments_dump_open }, + network_instruments_dump_can_write_encap, network_instruments_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_LANALYZER */ { "Novell LANalyzer","lanalyzer", "tr1", NULL, TRUE, FALSE, 0, - lanalyzer_dump_can_write_encap, lanalyzer_dump_open }, + lanalyzer_dump_can_write_encap, lanalyzer_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PPPDUMP */ { "pppd log (pppdump format)", "pppd", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_RADCOM */ { "RADCOM WAN/LAN analyzer", "radcom", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_SNOOP */ { "Sun snoop", "snoop", "snoop", "cap", FALSE, FALSE, 0, - snoop_dump_can_write_encap, snoop_dump_open }, + snoop_dump_can_write_encap, snoop_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_SHOMITI */ { "Shomiti/Finisar Surveyor", "shomiti", "cap", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_VMS */ { "TCPIPtrace (VMS)", "tcpiptrace", "txt", NULL, FALSE, FALSE, 0, - NULL, NULL}, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_K12 */ { "Tektronix K12xx 32-bit .rf5 format", "rf5", "rf5", NULL, TRUE, FALSE, 0, - k12_dump_can_write_encap, k12_dump_open }, + k12_dump_can_write_encap, k12_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_TOSHIBA */ { "Toshiba Compact ISDN Router snoop", "toshiba", "txt", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_VISUAL_NETWORKS */ { "Visual Networks traffic capture", "visual", NULL, NULL, TRUE, FALSE, 0, - visual_dump_can_write_encap, visual_dump_open }, + visual_dump_can_write_encap, visual_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PEEKCLASSIC_V56 */ { "WildPackets classic (V5 and V6)", "peekclassic56", "pkt", "tpc;apc;wpz", FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PEEKCLASSIC_V7 */ { "WildPackets classic (V7)", "peekclassic7", "pkt", "tpc;apc;wpz", FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PEEKTAGGED */ { "WildPackets tagged", "peektagged", "pkt", "tpc;apc;wpz", FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_MPEG */ { "MPEG", "mpeg", "mpeg", "mpg;mp3", FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_K12TEXT */ { "K12 text file", "k12text", "txt", NULL, FALSE, FALSE, 0, - k12text_dump_can_write_encap, k12text_dump_open }, + k12text_dump_can_write_encap, k12text_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETSCREEN */ { "NetScreen snoop text file", "netscreen", "txt", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_COMMVIEW */ { "TamoSoft CommView", "commview", "ncf", NULL, FALSE, FALSE, 0, - commview_dump_can_write_encap, commview_dump_open }, + commview_dump_can_write_encap, commview_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_BTSNOOP */ { "Symbian OS btsnoop", "btsnoop", "log", NULL, FALSE, FALSE, 0, - btsnoop_dump_can_write_encap, btsnoop_dump_open_h4 }, + btsnoop_dump_can_write_encap, btsnoop_dump_open_h4, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_TNEF */ { "Transport-Neutral Encapsulation Format", "tnef", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_DCT3TRACE */ { "Gammu DCT3 trace", "dct3trace", "xml", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_PACKETLOGGER */ { "PacketLogger", "pklg", "pklg", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_DAINTREE_SNA */ { "Daintree SNA", "dsna", "dcf", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETSCALER_1_0 */ { "NetScaler Trace (Version 1.0)", "nstrace10", NULL, NULL, TRUE, FALSE, 0, - nstrace_10_dump_can_write_encap, nstrace_dump_open }, + nstrace_10_dump_can_write_encap, nstrace_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_NETSCALER_2_0 */ { "NetScaler Trace (Version 2.0)", "nstrace20", "cap", NULL, TRUE, FALSE, 0, - nstrace_20_dump_can_write_encap, nstrace_dump_open }, + nstrace_20_dump_can_write_encap, nstrace_dump_open, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_JPEG_JFIF */ { "JPEG/JFIF", "jpeg", "jpg", "jpeg;jfif", FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_IPFIX */ { "IPFIX File Format", "ipfix", "pfx", "ipfix", FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_ENCAP_MIME */ { "MIME File Format", "mime", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_AETHRA */ { "Aethra .aps file", "aethra", "aps", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_MPEG_2_TS */ { "MPEG2 transport stream", "mp2t", "mp2t", "ts;mpg", FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_VWR_80211 */ { "Ixia IxVeriWave .vwr Raw 802.11 Capture", "vwr80211", "vwr", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_VWR_ETH */ { "Ixia IxVeriWave .vwr Raw Ethernet Capture", "vwreth", "vwr", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_CAMINS */ { "CAM Inspector file", "camins", "camins", NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_TYPE_SUBTYPE_STANAG_4607 */ { "STANAG 4607 Format", "stanag4607", NULL, NULL, FALSE, FALSE, 0, - NULL, NULL }, + NULL, NULL, NULL }, /* WTAP_FILE_NETSCALER_3_0 */ { "NetScaler Trace (Version 3.0)", "nstrace30", "cap", NULL, TRUE, FALSE, 0, - nstrace_30_dump_can_write_encap, nstrace_dump_open } + nstrace_30_dump_can_write_encap, nstrace_dump_open, NULL } }; @@ -1315,14 +1441,77 @@ static void init_file_types_subtypes(void) { dump_open_table = (const struct file_type_subtype_info*)(void *)dump_open_table_arr->data; } -int wtap_register_file_type_subtypes(const struct file_type_subtype_info* fi) { +/* if subtype is WTAP_FILE_TYPE_SUBTYPE_UNKNOWN, then create a new subtype as well as register it, else replace the + existing entry in that spot */ +int wtap_register_file_type_subtypes(const struct file_type_subtype_info* fi, const int subtype) { + struct file_type_subtype_info* finfo = NULL; init_file_types_subtypes(); - g_array_append_val(dump_open_table_arr,*fi); + if (!fi || !fi->name || !fi->short_name || subtype > wtap_num_file_types_subtypes) { + g_error("no file type info or invalid file type to register"); + return subtype; + } - dump_open_table = (const struct file_type_subtype_info*)(void *)dump_open_table_arr->data; + /* do we want a new registration? */ + if (subtype == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) { + /* register a new one; first verify there isn't one named this already */ + if (wtap_short_string_to_file_type_subtype(fi->short_name) > -1 ) { + g_error("file type short name already exists"); + return subtype; + } + + g_array_append_val(dump_open_table_arr,*fi); + + dump_open_table = (const struct file_type_subtype_info*)(void *)dump_open_table_arr->data; + + return wtap_num_file_types_subtypes++; + } - return wtap_num_file_types_subtypes++; + /* re-register an existing one - verify the short names do match (sanity check really) */ + if (!dump_open_table[subtype].short_name || strcmp(dump_open_table[subtype].short_name,fi->short_name) != 0) { + g_error("invalid file type name given to register"); + return subtype; + } + + /* yes, we're going to cast to change its const-ness */ + finfo = (struct file_type_subtype_info*)(&dump_open_table[subtype]); + /*finfo->name = fi->name;*/ + /*finfo->short_name = fi->short_name;*/ + finfo->default_file_extension = fi->default_file_extension; + finfo->additional_file_extensions = fi->additional_file_extensions; + finfo->writing_must_seek = fi->writing_must_seek; + finfo->has_name_resolution = fi->has_name_resolution; + finfo->supported_comment_types = fi->supported_comment_types; + finfo->can_write_encap = fi->can_write_encap; + finfo->dump_open = fi->dump_open; + finfo->wslua_info = fi->wslua_info; + + return subtype; +} + +/* De-registers a file writer - they can never be removed from the GArray, but we can "clear" an entry. + */ +void wtap_deregister_file_type_subtype(const int subtype) { + struct file_type_subtype_info* finfo = NULL; + + if (subtype < 0 || subtype >= wtap_num_file_types_subtypes) { + g_error("invalid file type to de-register"); + return; + } + + /* yes, we're going to cast to change its const-ness */ + finfo = (struct file_type_subtype_info*)(&dump_open_table[subtype]); + /* unfortunately, it's not safe to null-out the name or short_name; bunch of other code doesn't guard aainst that, afaict */ + /*finfo->name = NULL;*/ + /*finfo->short_name = NULL;*/ + finfo->default_file_extension = NULL; + finfo->additional_file_extensions = NULL; + finfo->writing_must_seek = FALSE; + finfo->has_name_resolution = FALSE; + finfo->supported_comment_types = 0; + finfo->can_write_encap = NULL; + finfo->dump_open = NULL; + finfo->wslua_info = NULL; } int wtap_get_num_file_types_subtypes(void) @@ -1352,12 +1541,27 @@ wtap_dump_file_encap_type(const GArray *file_encaps) static gboolean wtap_dump_can_write_encap(int filetype, int encap) { + int result = 0; + if (filetype < 0 || filetype >= wtap_num_file_types_subtypes || dump_open_table[filetype].can_write_encap == NULL) return FALSE; - if ((*dump_open_table[filetype].can_write_encap)(encap) != 0) - return FALSE; + result = (*dump_open_table[filetype].can_write_encap)(encap); + + if (result != 0) { + /* if the err said to check wslua's can_write_encap, try that */ + if (result == WTAP_ERR_CHECK_WSLUA + && dump_open_table[filetype].wslua_info != NULL + && dump_open_table[filetype].wslua_info->wslua_can_write_encap != NULL) { + + result = (*dump_open_table[filetype].wslua_info->wslua_can_write_encap)(encap, dump_open_table[filetype].wslua_info->wslua_data); + + } + + if (result != 0) + return FALSE; + } return TRUE; } @@ -1898,6 +2102,15 @@ static gboolean wtap_dump_open_check(int file_type_subtype, int encap, gboolean /* OK, we know how to write that type; can we write the specified encapsulation type? */ *err = (*dump_open_table[file_type_subtype].can_write_encap)(encap); + /* if the err said to check wslua's can_write_encap, try that */ + if (*err == WTAP_ERR_CHECK_WSLUA + && dump_open_table[file_type_subtype].wslua_info != NULL + && dump_open_table[file_type_subtype].wslua_info->wslua_can_write_encap != NULL) { + + *err = (*dump_open_table[file_type_subtype].wslua_info->wslua_can_write_encap)(encap, dump_open_table[file_type_subtype].wslua_info->wslua_data); + + } + if (*err != 0) return FALSE; @@ -1907,10 +2120,6 @@ static gboolean wtap_dump_open_check(int file_type_subtype, int encap, gboolean return FALSE; } - *err = (*dump_open_table[file_type_subtype].can_write_encap)(encap); - if (*err != 0) - return FALSE; - /* All systems go! */ return TRUE; } @@ -1930,6 +2139,7 @@ static wtap_dumper* wtap_dump_alloc_wdh(int file_type_subtype, int encap, int sn wdh->snaplen = snaplen; wdh->encap = encap; wdh->compressed = compressed; + wdh->wslua_data = NULL; return wdh; } @@ -1959,6 +2169,12 @@ static gboolean wtap_dump_open_finish(wtap_dumper *wdh, int file_type_subtype, g return FALSE; } + /* Set wdh with wslua data if any - this is how we pass the data + * to the file writer. + */ + if (dump_open_table[file_type_subtype].wslua_info) + wdh->wslua_data = dump_open_table[file_type_subtype].wslua_info->wslua_data; + /* Now try to open the file for writing. */ if (!(*dump_open_table[file_type_subtype].dump_open)(wdh, err)) { return FALSE; diff --git a/wiretap/file_wrappers.c b/wiretap/file_wrappers.c index 1bfccf0570..0f30493ee6 100644 --- a/wiretap/file_wrappers.c +++ b/wiretap/file_wrappers.c @@ -1208,6 +1208,53 @@ file_read(void *buf, unsigned int len, FILE_T file) } /* + * XXX - this *peeks* at next byte, not a character. + */ +int +file_peekc(FILE_T file) +{ + int ret = 0; + + /* check that we're reading and that there's no error */ + if (file->err) + return -1; + + /* try output buffer (no need to check for skip request) */ + if (file->have) { + return *(file->next); + } + + /* process a skip request */ + if (file->seek_pending) { + file->seek_pending = FALSE; + if (gz_skip(file, file->skip) == -1) + return -1; + } + /* if we processed a skip request, there may be data in the buffer, + * or an error could have occured; likewise if we didn't do seek but + * now call fill_out_buffer, the errors can occur. So we do this while + * loop to check before and after - this is basically the logic from + * file_read() but only for peeking not consuming a byte + */ + while (1) { + if (file->have) { + return *(file->next); + } + else if (file->err) { + return -1; + } + else if (file->eof && file->avail_in == 0) { + return -1; + } + else if (fill_out_buffer(file) == -1) { + return -1; + } + } + /* it's actually impossible to get here */ + return ret; +} + +/* * XXX - this gets a byte, not a character. */ int diff --git a/wiretap/file_wrappers.h b/wiretap/file_wrappers.h index c32d380180..7f3cdc4205 100644 --- a/wiretap/file_wrappers.h +++ b/wiretap/file_wrappers.h @@ -22,7 +22,7 @@ #define __WTAP_FILE_WRAPPERS_H__ #include <glib.h> -#include <wtap.h> +#include "wtap.h" #include <wsutil/file_util.h> #include "ws_symbol_export.h" @@ -34,8 +34,9 @@ extern gboolean file_skip(FILE_T file, gint64 delta, int *err); WS_DLL_PUBLIC gint64 file_tell(FILE_T stream); extern gint64 file_tell_raw(FILE_T stream); extern int file_fstat(FILE_T stream, ws_statb64 *statb, int *err); -extern gboolean file_iscompressed(FILE_T stream); +WS_DLL_PUBLIC gboolean file_iscompressed(FILE_T stream); WS_DLL_PUBLIC int file_read(void *buf, unsigned int count, FILE_T file); +WS_DLL_PUBLIC int file_peekc(FILE_T stream); WS_DLL_PUBLIC int file_getc(FILE_T stream); WS_DLL_PUBLIC char *file_gets(char *buf, int len, FILE_T stream); WS_DLL_PUBLIC int file_eof(FILE_T stream); diff --git a/wiretap/wtap-int.h b/wiretap/wtap-int.h index 8c186b40ec..1d47f25423 100644 --- a/wiretap/wtap-int.h +++ b/wiretap/wtap-int.h @@ -58,7 +58,8 @@ struct wtap { guint number_of_interfaces; /**< The number of interfaces a capture was made on, number of IDB:s in a pcapng file or equivalent(?)*/ GArray *interface_data; /**< An array holding the interface data from pcapng IDB:s or equivalent(?)*/ - void *priv; + void *priv; /* this one holds per-file state and is free'd automatically by wtap_close() */ + void *wslua_data; /* this one holds wslua state info and is not free'd */ subtype_read_func subtype_read; subtype_seek_read_func subtype_seek_read; @@ -97,7 +98,8 @@ struct wtap_dumper { gboolean compressed; gint64 bytes_dumped; - void *priv; + void *priv; /* this one holds per-file state and is free'd automatically by wtap_dump_close() */ + void *wslua_data; /* this one holds wslua state info and is not free'd */ subtype_write_func subtype_write; subtype_close_func subtype_close; @@ -111,10 +113,10 @@ struct wtap_dumper { GArray *interface_data; /**< An array holding the interface data from pcapng IDB:s or equivalent(?) NULL if not present.*/ }; -gboolean wtap_dump_file_write(wtap_dumper *wdh, const void *buf, +WS_DLL_PUBLIC gboolean wtap_dump_file_write(wtap_dumper *wdh, const void *buf, size_t bufsize, int *err); -gint64 wtap_dump_file_seek(wtap_dumper *wdh, gint64 offset, int whence, int *err); -gint64 wtap_dump_file_tell(wtap_dumper *wdh, int *err); +WS_DLL_PUBLIC gint64 wtap_dump_file_seek(wtap_dumper *wdh, gint64 offset, int whence, int *err); +WS_DLL_PUBLIC gint64 wtap_dump_file_tell(wtap_dumper *wdh, int *err); extern gint wtap_num_file_types; @@ -280,6 +282,7 @@ GSList *wtap_get_compressed_file_extensions(void); * header, so if we get an EOF trying to read the packet data, the file * has been cut short, even if the read didn't read any data at all.) */ +WS_DLL_PUBLIC gboolean wtap_read_packet_bytes(FILE_T fh, Buffer *buf, guint length, int *err, gchar **err_info); diff --git a/wiretap/wtap.h b/wiretap/wtap.h index f61b51f111..d0bc4747cb 100644 --- a/wiretap/wtap.h +++ b/wiretap/wtap.h @@ -1134,6 +1134,22 @@ typedef struct wtap_dumper wtap_dumper; typedef struct wtap_reader *FILE_T; +/* Similar to the wtap_open_routine_info for open routines, the following + * wtap_wslua_file_info struct is used by wslua code for Lua-based file writers. + * + * This concept is necessary because when wslua goes to invoke the + * registered dump/write_open routine callback in Lua, it needs the ref number representing + * the hooked function inside Lua. This will be stored in the thing pointed to + * by the void* data here. This 'data' pointer will be copied into the + * wtap_dumper struct's 'void* data' member when calling the dump_open function, + * which is how wslua finally retrieves it. Unlike wtap_dumper's 'priv' member, its + * 'data' member is not free'd in wtap_dump_close(). + */ +typedef struct wtap_wslua_file_info { + int (*wslua_can_write_encap)(int, void*); /* a can_write_encap func for wslua uses */ + void* wslua_data; /* holds the wslua data */ +} wtap_wslua_file_info_t; + /* * For registering extensions used for capture file formats. * @@ -1218,6 +1234,7 @@ struct open_info { int type; wtap_open_routine_t open_routine; const char *extensions; + void* wslua_data; /* should be NULL for C-code file readers */ }; WS_DLL_PUBLIC struct open_info *open_routines; @@ -1264,6 +1281,10 @@ struct file_type_subtype_info { /* the function to open the capture file for writing */ /* should be NULL is this file type don't have write support */ int (*dump_open)(wtap_dumper *, int *); + + /* if can_write_encap returned WTAP_ERR_CHECK_WSLUA, then this is used instead */ + /* this should be NULL for everyone except Lua-based file writers */ + wtap_wslua_file_info_t *wslua_info; }; #define WTAP_TYPE_AUTO 0 @@ -1488,18 +1509,21 @@ WS_DLL_PUBLIC void register_all_wiretap_modules(void); WS_DLL_PUBLIC void wtap_register_file_type_extension(const struct file_extension_info *ei); -#if 0 + WS_DLL_PUBLIC -void wtap_register_magic_number_open_routine(wtap_open_routine_t open_routine); +void wtap_register_open_info(const struct open_info *oi, const gboolean first_routine); WS_DLL_PUBLIC -void wtap_register_heuristic_open_info(const struct heuristic_open_info *oi); -#endif +gboolean wtap_has_open_info(const gchar *name); WS_DLL_PUBLIC -void wtap_register_open_info(const struct open_info *oi); +void wtap_deregister_open_info(const gchar *name); + WS_DLL_PUBLIC unsigned int open_info_name_to_type(const char *name); WS_DLL_PUBLIC -int wtap_register_file_type_subtypes(const struct file_type_subtype_info* fi); +int wtap_register_file_type_subtypes(const struct file_type_subtype_info* fi, const int subtype); +WS_DLL_PUBLIC +void wtap_deregister_file_type_subtype(const int file_type_subtype); + WS_DLL_PUBLIC int wtap_register_encap_type(const char* name, const char* short_name); @@ -1582,6 +1606,10 @@ int wtap_register_encap_type(const char* name, const char* short_name); /** Packet being written is larger than we support; do not use when reading, use WTAP_ERR_BAD_FILE instead */ +#define WTAP_ERR_CHECK_WSLUA -25 + /** Not really an error: the file type being checked is from a Lua + plugin, so that the code will call wslua_can_write_encap() instead if it gets this */ + #ifdef __cplusplus } #endif /* __cplusplus */ |