/* decode_as_model.cpp * Data model for Decode As records. * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "decode_as_model.h" #include #include #include #include #include #include #include #include #include #include #include #include static const char *DEFAULT_TABLE = "tcp.port"; // Arbitrary static const char *DEFAULT_UI_TABLE = "TCP port"; // Arbitrary DecodeAsItem::DecodeAsItem(const char* table_name, gconstpointer selector) : tableName_(DEFAULT_TABLE), tableUIName_(DEFAULT_UI_TABLE), selectorUint_(0), selectorString_(""), selectorDCERPC_(NULL), default_dissector_(DECODE_AS_NONE), current_dissector_(DECODE_AS_NONE), dissector_handle_(NULL) { if (table_name == nullptr) return; init(table_name, selector); } DecodeAsItem::DecodeAsItem(const decode_as_t *entry, gconstpointer selector) : tableName_(DEFAULT_TABLE), tableUIName_(DEFAULT_UI_TABLE), selectorUint_(0), selectorString_(""), selectorDCERPC_(NULL), default_dissector_(DECODE_AS_NONE), current_dissector_(DECODE_AS_NONE), dissector_handle_(NULL) { if (entry == nullptr) return; init(entry->table_name, selector); } DecodeAsItem::~DecodeAsItem() { } void DecodeAsItem::init(const char* table_name, gconstpointer selector) { tableName_ = table_name; tableUIName_ = get_dissector_table_ui_name(tableName_); dissector_handle_t default_handle = NULL; ftenum_t selector_type = get_dissector_table_selector_type(tableName_); if (FT_IS_STRING(selector_type)) { if (selector != NULL) { default_handle = dissector_get_default_string_handle(tableName_, (const gchar*)selector); selectorString_ = QString((const char*)selector); } } else if (FT_IS_UINT(selector_type)) { if (selector != NULL) { selectorUint_ = GPOINTER_TO_UINT(selector); default_handle = dissector_get_default_uint_handle(tableName_, selectorUint_); } } else if (selector_type == FT_NONE) { // There is no default for an FT_NONE dissector table } else if (selector_type == FT_GUID) { /* Special handling for DCE/RPC dissectors */ if (strcmp(tableName_, DCERPC_TABLE_NAME) == 0) { selectorDCERPC_ = (decode_dcerpc_bind_values_t*)(selector); } } if (default_handle != NULL) { default_dissector_ = dissector_handle_get_description(default_handle); // When adding a new record, we set the "current" values equal to // the default, so the user can easily reset the value. // The existing value read from the prefs file should already // be added to the table from reading the prefs file. // When reading existing values the current dissector should be // set explicitly to the actual current value. current_dissector_ = default_dissector_; dissector_handle_ = default_handle; } } void DecodeAsItem::setTable(const decode_as_t *entry) { if (entry == nullptr) return; tableName_ = entry->table_name; tableUIName_ = get_dissector_table_ui_name(entry->table_name); /* XXX: Should the selector values be reset (e.g., to 0 and "") * What if someone tries to change the table to the DCERPC table? * That doesn't really work without the DCERPC special handling. */ updateHandles(); } void DecodeAsItem::setSelector(const QString &value) { ftenum_t selector_type = get_dissector_table_selector_type(tableName_); if (FT_IS_STRING(selector_type)) { selectorString_ = value; } else if (FT_IS_UINT(selector_type)) { selectorUint_ = value.toUInt(Q_NULLPTR, 0); } updateHandles(); } void DecodeAsItem::setDissectorHandle(dissector_handle_t handle) { dissector_handle_ = handle; if (handle == nullptr) { current_dissector_ = DECODE_AS_NONE; } else { current_dissector_ = dissector_handle_get_description(handle); } } void DecodeAsItem::updateHandles() { ftenum_t selector_type = get_dissector_table_selector_type(tableName_); dissector_handle_t default_handle = nullptr; if (FT_IS_STRING(selector_type)) { default_handle = dissector_get_default_string_handle(tableName_, qUtf8Printable(selectorString_)); } else if (FT_IS_UINT(selector_type)) { default_handle = dissector_get_default_uint_handle(tableName_, selectorUint_); } if (default_handle != nullptr) { default_dissector_ = dissector_handle_get_description(default_handle); } else { default_dissector_ = DECODE_AS_NONE; } } DecodeAsModel::DecodeAsModel(QObject *parent, capture_file *cf) : QAbstractTableModel(parent), cap_file_(cf) { } DecodeAsModel::~DecodeAsModel() { foreach(DecodeAsItem* item, decode_as_items_) delete item; decode_as_items_.clear(); } Qt::ItemFlags DecodeAsModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemFlags(); DecodeAsItem* item = decode_as_items_[index.row()]; Qt::ItemFlags flags = QAbstractTableModel::flags(index); switch(index.column()) { case DecodeAsModel::colTable: case DecodeAsModel::colProtocol: flags |= Qt::ItemIsEditable; break; case DecodeAsModel::colSelector: { ftenum_t selector_type = get_dissector_table_selector_type(item->tableName()); if ((selector_type != FT_NONE) && (item->selectorDCERPC() == NULL)) flags |= Qt::ItemIsEditable; break; } } return flags; } QVariant DecodeAsModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } DecodeAsItem* item; switch (role) { case Qt::ToolTipRole: switch (index.column()) { case colTable: return tr("Match using this field"); case colSelector: return tr("Change behavior when the field matches this value"); case colType: return tr("Field value type (and base, if Integer)"); case colDefault: return tr("Default \"Decode As\" behavior"); case colProtocol: return tr("Current\"Decode As\" behavior"); } return QVariant(); case Qt::DisplayRole: case Qt::EditRole: item = decode_as_items_[index.row()]; if (item == NULL) return QVariant(); switch (index.column()) { case colTable: return item->tableUIName(); case colSelector: { ftenum_t selector_type = get_dissector_table_selector_type(item->tableName()); if (FT_IS_UINT(selector_type)) { return entryString(item->tableName(), GUINT_TO_POINTER(item->selectorUint())); } else if (FT_IS_STRING(selector_type)) { return entryString(item->tableName(), (gconstpointer)item->selectorString().toUtf8().constData()); } else if (selector_type == FT_GUID) { if (item->selectorDCERPC() != NULL) { return item->selectorDCERPC()->ctx_id; } } return DECODE_AS_NONE; } case colType: { ftenum_t selector_type = get_dissector_table_selector_type(item->tableName()); if (FT_IS_STRING(selector_type)) { return tr("String"); } else if (FT_IS_UINT(selector_type)) { QString type_desc = tr("Integer, base "); switch (get_dissector_table_param(item->tableName())) { case BASE_OCT: type_desc.append("8"); break; case BASE_DEC: type_desc.append("10"); break; case BASE_HEX: type_desc.append("16"); break; default: type_desc.append(tr("unknown")); } return type_desc; } else if (selector_type == FT_NONE) { return tr(""); } else if (selector_type == FT_GUID) { if (item->selectorDCERPC() != NULL) { return QString("ctx_id"); } else { return tr("GUID"); } } break; } case colDefault: return item->defaultDissector(); case colProtocol: return item->currentDissector(); } return QVariant(); case Qt::UserRole: item = decode_as_items_[index.row()]; return QVariant::fromValue(static_cast(item)); } return QVariant(); } QVariant DecodeAsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal) return QVariant(); switch (section) { case colTable: return tr("Field"); case colSelector: return tr("Value"); case colType: return tr("Type"); case colDefault: return tr("Default"); case colProtocol: return tr("Current"); default: ws_assert_not_reached(); } return QVariant(); } int DecodeAsModel::rowCount(const QModelIndex &parent) const { // there are no children if (parent.isValid()) { return 0; } return static_cast(decode_as_items_.count()); } int DecodeAsModel::columnCount(const QModelIndex &parent) const { // there are no children if (parent.isValid()) { return 0; } return colDecodeAsMax; } bool DecodeAsModel::setData(const QModelIndex &cur_index, const QVariant &value, int role) { if (!cur_index.isValid()) return false; if (role != Qt::EditRole) return false; if (data(cur_index, role) == value) { // Data appears unchanged, do not do additional checks. return true; } DecodeAsItem* item = decode_as_items_[cur_index.row()]; switch(cur_index.column()) { case DecodeAsModel::colTable: { QString valueStr = value.toString(); //grab the table values from the Decode As list because they are persistent for (GList *cur = decode_as_list; cur; cur = cur->next) { decode_as_t *entry = (decode_as_t *) cur->data; if (valueStr.compare(get_dissector_table_ui_name(entry->table_name)) == 0) { item->setTable(entry); //all other columns affected emit dataChanged(index(cur_index.row(), colSelector), index(cur_index.row(), colProtocol)); break; } } } break; case DecodeAsModel::colProtocol: { dissector_handle_t handle = VariantPointer::asPtr(value); item->setDissectorHandle(handle); break; } case DecodeAsModel::colSelector: item->setSelector(value.toString()); emit dataChanged(index(cur_index.row(), colDefault), index(cur_index.row(), colProtocol)); break; } return true; } bool DecodeAsModel::insertRows(int row, int count, const QModelIndex &/*parent*/) { // support insertion of just one item for now. if (count != 1 || row < 0 || row > rowCount()) return false; beginInsertRows(QModelIndex(), row, row); DecodeAsItem* item = nullptr; const decode_as_t *firstEntry = nullptr; if (cap_file_ && cap_file_->edt) { // Populate the new Decode As item with the last protocol layer // that can support Decode As and has a selector field for that // present in the frame. // // XXX: This treats 0 (for UInts) and empty strings the same as // the fields for the tables not being present at all. wmem_list_frame_t * protos = wmem_list_tail(cap_file_->edt->pi.layers); gint8 curr_layer_num_saved = cap_file_->edt->pi.curr_layer_num; guint8 curr_layer_num = wmem_list_count(cap_file_->edt->pi.layers); while (protos != NULL && item == nullptr) { int proto_id = GPOINTER_TO_INT(wmem_list_frame_data(protos)); const gchar * proto_name = proto_get_protocol_filter_name(proto_id); for (GList *cur = decode_as_list; cur; cur = cur->next) { decode_as_t *entry = (decode_as_t *) cur->data; if (g_strcmp0(proto_name, entry->name) == 0) { if (firstEntry == nullptr) { firstEntry = entry; } ftenum_t selector_type = get_dissector_table_selector_type(entry->table_name); // Pick the first value in the packet for the current // layer for the table // XXX: What if the Decode As table supports multiple // values, but the first possible one is 0/NULL? cap_file_->edt->pi.curr_layer_num = curr_layer_num; gpointer selector = entry->values[0].build_values[0](&cap_file_->edt->pi); // FT_NONE tables don't need a value if (selector != NULL || selector_type == FT_NONE) { item = new DecodeAsItem(entry, selector); break; } } } protos = wmem_list_frame_prev(protos); curr_layer_num--; } cap_file_->edt->pi.curr_layer_num = curr_layer_num_saved; } // If we didn't find an entry with a valid selector, create an entry // from the last table with an empty selector, or an empty entry. if (item == nullptr) { item = new DecodeAsItem(firstEntry); } decode_as_items_ << item; endInsertRows(); return true; } bool DecodeAsModel::removeRows(int row, int count, const QModelIndex &/*parent*/) { if (count != 1 || row < 0 || row >= rowCount()) return false; beginRemoveRows(QModelIndex(), row, row); DecodeAsItem* item = decode_as_items_.takeAt(row); delete item; endRemoveRows(); return true; } void DecodeAsModel::clearAll() { if (rowCount() < 1) return; beginResetModel(); foreach(DecodeAsItem* item, decode_as_items_) delete item; decode_as_items_.clear(); endResetModel(); } bool DecodeAsModel::copyRow(int dst_row, int src_row) { if (src_row < 0 || src_row >= rowCount() || dst_row < 0 || dst_row >= rowCount()) { return false; } DecodeAsItem* src = decode_as_items_[src_row]; DecodeAsItem* dst = decode_as_items_[dst_row]; *dst = *src; QVector roles; roles << Qt::EditRole << Qt::BackgroundRole; emit dataChanged(index(dst_row, 0), index(dst_row, columnCount()), roles); return true; } prefs_set_pref_e DecodeAsModel::readDecodeAsEntry(gchar *key, const gchar *value, void *private_data, gboolean) { DecodeAsModel *model = (DecodeAsModel*)private_data; if (model == NULL) return PREFS_SET_OK; if (strcmp(key, DECODE_AS_ENTRY) != 0) { return PREFS_SET_NO_SUCH_PREF; } /* Parse into table, selector, initial, current */ gchar **values = g_strsplit_set(value, ",", 4); DecodeAsItem *item = nullptr; dissector_table_t dissector_table = find_dissector_table(values[0]); QString tableName(values[0]); // Get the table values from the Decode As list because they are persistent for (GList *cur = decode_as_list; cur; cur = cur->next) { decode_as_t *entry = (decode_as_t *) cur->data; if (tableName.compare(entry->table_name) == 0) { item = new DecodeAsItem(entry); break; } } if (item == nullptr) { g_strfreev(values); return PREFS_SET_SYNTAX_ERR; } QString selector(values[1]); item->setSelector(selector); /* The value for the default dissector in the decode_as_entries file * has no effect other than perhaps making the config file more * informative when edited manually. * We will actually display and reset to the programmatic default value. */ item->setDissectorHandle(dissector_table_get_dissector_handle(dissector_table, values[3])); model->decode_as_items_ << item; g_strfreev(values); return PREFS_SET_OK; } bool DecodeAsModel::copyFromProfile(QString filename, const gchar **err) { FILE *fp = ws_fopen(filename.toUtf8().constData(), "r"); if (fp == NULL) { *err = g_strerror(errno); return false; } beginInsertRows(QModelIndex(), rowCount(), rowCount()); read_prefs_file(filename.toUtf8().constData(), fp, readDecodeAsEntry, this); endInsertRows(); fclose(fp); return true; } QString DecodeAsModel::entryString(const gchar *table_name, gconstpointer value) { QString entry_str; ftenum_t selector_type = get_dissector_table_selector_type(table_name); switch (selector_type) { case FT_UINT8: case FT_UINT16: case FT_UINT24: case FT_UINT32: { uint num_val = GPOINTER_TO_UINT(value); switch (get_dissector_table_param(table_name)) { case BASE_DEC: entry_str = QString::number(num_val); break; case BASE_HEX: int width; switch (selector_type) { case FT_UINT8: width = 2; break; case FT_UINT16: width = 4; break; case FT_UINT24: width = 6; break; case FT_UINT32: width = 8; break; default: ws_assert_not_reached(); break; } entry_str = QString("%1").arg(int_to_qstring(num_val, width, 16)); break; case BASE_OCT: entry_str = "0" + QString::number(num_val, 8); break; } break; } case FT_STRING: case FT_STRINGZ: case FT_UINT_STRING: case FT_STRINGZPAD: case FT_STRINGZTRUNC: entry_str = (const char *)value; break; case FT_GUID: //avoid the assert for now break; case FT_NONE: //doesn't really matter, just avoiding the assert return "0"; default: ws_assert_not_reached(); break; } return entry_str; } void DecodeAsModel::fillTable() { decode_as_items_.clear(); beginResetModel(); dissector_all_tables_foreach_changed(buildChangedList, this); decode_dcerpc_add_show_list(buildDceRpcChangedList, this); endResetModel(); } void DecodeAsModel::setDissectorHandle(const QModelIndex &index, dissector_handle_t dissector_handle) { DecodeAsItem* item = decode_as_items_[index.row()]; if (item != NULL) item->setDissectorHandle(dissector_handle); } void DecodeAsModel::buildChangedList(const gchar *table_name, ftenum_t, gpointer key, gpointer value, gpointer user_data) { DecodeAsModel *model = (DecodeAsModel*)user_data; if (model == NULL) return; dissector_handle_t current_dh; DecodeAsItem* item = new DecodeAsItem(table_name, key); current_dh = dtbl_entry_get_handle((dtbl_entry_t *)value); item->setDissectorHandle(current_dh); model->decode_as_items_ << item; } void DecodeAsModel::buildDceRpcChangedList(gpointer data, gpointer user_data) { dissector_table_t sub_dissectors; guid_key guid_val; decode_dcerpc_bind_values_t *binding = (decode_dcerpc_bind_values_t *)data; DecodeAsModel *model = (DecodeAsModel*)user_data; if (model == NULL) return; DecodeAsItem* item = new DecodeAsItem(DCERPC_TABLE_NAME, binding); sub_dissectors = find_dissector_table(DCERPC_TABLE_NAME); guid_val.ver = binding->ver; guid_val.guid = binding->uuid; item->setDissectorHandle(dissector_get_guid_handle(sub_dissectors, &guid_val)); model->decode_as_items_ << item; } typedef QPair UintPair; typedef QPair CharPtrPair; void DecodeAsModel::gatherChangedEntries(const gchar *table_name, ftenum_t selector_type, gpointer key, gpointer, gpointer user_data) { DecodeAsModel *model = qobject_cast((DecodeAsModel*)user_data); if (model == NULL) return; switch (selector_type) { case FT_UINT8: case FT_UINT16: case FT_UINT24: case FT_UINT32: model->changed_uint_entries_ << UintPair(table_name, GPOINTER_TO_UINT(key)); break; case FT_NONE: //need to reset dissector table, so this needs to be in a changed list, //might as well be the uint one. model->changed_uint_entries_ << UintPair(table_name, 0); break; case FT_STRING: case FT_STRINGZ: case FT_UINT_STRING: case FT_STRINGZPAD: case FT_STRINGZTRUNC: model->changed_string_entries_ << CharPtrPair(table_name, (const char *) key); break; default: break; } } void DecodeAsModel::applyChanges() { dissector_table_t sub_dissectors; module_t *module; pref_t* pref_value; dissector_handle_t handle; // Reset all dissector tables, then apply all rules from model. // We can't call g_hash_table_removed from g_hash_table_foreach, which // means we can't call dissector_reset_{string,uint} from // dissector_all_tables_foreach_changed. Collect changed entries in // lists and remove them separately. // // If dissector_all_tables_remove_changed existed we could call it // instead. dissector_all_tables_foreach_changed(gatherChangedEntries, this); foreach (UintPair uint_entry, changed_uint_entries_) { /* Set "Decode As preferences" to default values */ sub_dissectors = find_dissector_table(uint_entry.first); handle = dissector_get_uint_handle(sub_dissectors, uint_entry.second); if (handle != NULL) { module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(handle))); pref_value = prefs_find_preference(module, uint_entry.first); if (pref_value != NULL) { module->prefs_changed_flags |= prefs_get_effect_flags(pref_value); reset_pref(pref_value); } } dissector_reset_uint(uint_entry.first, uint_entry.second); } changed_uint_entries_.clear(); foreach (CharPtrPair char_ptr_entry, changed_string_entries_) { dissector_reset_string(char_ptr_entry.first, char_ptr_entry.second); } changed_string_entries_.clear(); foreach(DecodeAsItem *item, decode_as_items_) { decode_as_t *decode_as_entry; if (item->currentDissector().isEmpty()) { continue; } for (GList *cur = decode_as_list; cur; cur = cur->next) { decode_as_entry = (decode_as_t *) cur->data; if (!g_strcmp0(decode_as_entry->table_name, item->tableName())) { ftenum_t selector_type = get_dissector_table_selector_type(item->tableName()); gconstpointer selector_value; QByteArray byteArray; switch (selector_type) { case FT_UINT8: case FT_UINT16: case FT_UINT24: case FT_UINT32: selector_value = GUINT_TO_POINTER(item->selectorUint()); break; case FT_STRING: case FT_STRINGZ: case FT_UINT_STRING: case FT_STRINGZPAD: case FT_STRINGZTRUNC: byteArray = item->selectorString().toUtf8(); selector_value = (gconstpointer) byteArray.constData(); break; case FT_NONE: //selector value is ignored, but dissector table needs to happen selector_value = NULL; break; case FT_GUID: if (item->selectorDCERPC() != NULL) { selector_value = (gconstpointer)item->selectorDCERPC(); } else { //TODO: Support normal GUID dissector tables selector_value = NULL; } break; default: continue; } if ((item->currentDissector() == item->defaultDissector())) { decode_as_entry->reset_value(decode_as_entry->table_name, selector_value); sub_dissectors = find_dissector_table(decode_as_entry->table_name); /* For now, only numeric dissector tables can use preferences */ if (FT_IS_UINT(dissector_table_get_type(sub_dissectors))) { if (item->dissectorHandle() != NULL) { module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(item->dissectorHandle()))); pref_value = prefs_find_preference(module, decode_as_entry->table_name); if (pref_value != NULL) { module->prefs_changed_flags |= prefs_get_effect_flags(pref_value); prefs_remove_decode_as_value(pref_value, item->selectorUint(), TRUE); } } } break; } else { decode_as_entry->change_value(decode_as_entry->table_name, selector_value, item->dissectorHandle(), item->currentDissector().toUtf8().constData()); sub_dissectors = find_dissector_table(decode_as_entry->table_name); /* For now, only numeric dissector tables can use preferences */ if (item->dissectorHandle() != NULL) { if (FT_IS_UINT(dissector_table_get_type(sub_dissectors))) { module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(item->dissectorHandle()))); pref_value = prefs_find_preference(module, decode_as_entry->table_name); if (pref_value != NULL) { module->prefs_changed_flags |= prefs_get_effect_flags(pref_value); prefs_add_decode_as_value(pref_value, item->selectorUint(), FALSE); } } } break; } } } } prefs_apply_all(); }