# This file is part of Scapy
# Scapy 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
# any later version.
#
# Scapy 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 Scapy. If not, see <http://www.gnu.org/licenses/>.

# scapy.contrib.description = VLAN Trunking Protocol (VTP)
# scapy.contrib.status = loads

r"""
    VTP Scapy Extension
    ~~~~~~~~~~~~~~~~~~~~~

    :version:   2009-02-15
    :copyright: 2009 by Jochen Bartl
    :e-mail:    lobo@c3a.de / jochen.bartl@gmail.com
    :license:   GPL v2

        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.

    :TODO:

        - Join messages
        - RE MD5 hash calculation
        - Have a closer look at 8 byte padding in summary adv:

            "debug sw-vlan vtp packets" says the TLV length is invalid,
            when I change the values:

            ``b'\x00\x00\x00\x01\x06\x01\x00\x02'``

            * \x00\x00 ?
            * \x00\x01 tlvtype?
            * \x06 length?
            * \x00\x02 value?

        - h2i function for VTPTimeStampField

    :References:

        - | Understanding VLAN Trunk Protocol (VTP)
          | http://www.cisco.com/en/US/tech/tk389/tk689/technologies_tech_note09186a0080094c52.shtml  # noqa: E501
"""

from scapy.packet import Packet, bind_layers
from scapy.fields import ByteEnumField, ByteField, ConditionalField, \
    FieldLenField, IPField, PacketListField, ShortField, SignedIntField, \
    StrFixedLenField, StrLenField, XIntField
from scapy.layers.l2 import SNAP
from scapy.config import conf

_VTP_VLAN_TYPE = {
    1: 'Ethernet',
    2: 'FDDI',
    3: 'TrCRF',
    4: 'FDDI-net',
    5: 'TrBRF'
}

_VTP_VLANINFO_TLV_TYPE = {
    0x01: 'Source-Routing Ring Number',
    0x02: 'Source-Routing Bridge Number',
    0x03: 'Spanning-Tree Protocol Type',
    0x04: 'Parent VLAN',
    0x05: 'Translationally Bridged VLANs',
    0x06: 'Pruning',
    0x07: 'Bridge Type',
    0x08: 'Max ARE Hop Count',
    0x09: 'Max STE Hop Count',
    0x0A: 'Backup CRF Mode'
}


class VTPVlanInfoTlv(Packet):
    name = "VTP VLAN Info TLV"
    fields_desc = [
        ByteEnumField("type", 0, _VTP_VLANINFO_TLV_TYPE),
        ByteField("length", 0),
        StrLenField("value", None, length_from=lambda pkt: pkt.length + 1)
    ]

    def guess_payload_class(self, p):
        return conf.padding_layer


class VTPVlanInfo(Packet):
    name = "VTP VLAN Info"
    fields_desc = [
        ByteField("len", None),
        ByteEnumField("status", 0, {0: "active", 1: "suspended"}),
        ByteEnumField("type", 1, _VTP_VLAN_TYPE),
        FieldLenField("vlannamelen", None, "vlanname", "B"),
        ShortField("vlanid", 1),
        ShortField("mtu", 1500),
        XIntField("dot10index", None),
        StrLenField("vlanname", "default",
                    length_from=lambda pkt: 4 * ((pkt.vlannamelen + 3) // 4)),
        ConditionalField(
            PacketListField(
                "tlvlist", [], VTPVlanInfoTlv,
                length_from=lambda pkt: pkt.len - 12 - (4 * ((pkt.vlannamelen + 3) // 4))  # noqa: E501
            ),
            lambda pkt:pkt.type not in [1, 2]
        )
    ]

    def post_build(self, p, pay):
        vlannamelen = 4 * ((len(self.vlanname) + 3) // 4)

        if self.len is None:
            tmp_len = vlannamelen + 12
            p = chr(tmp_len & 0xff) + p[1:]

        # Pad vlan name with zeros if vlannamelen > len(vlanname)
        tmp_len = vlannamelen - len(self.vlanname)
        if tmp_len != 0:
            p += b"\x00" * tmp_len

        p += pay

        return p

    def guess_payload_class(self, p):
        return conf.padding_layer


_VTP_Types = {
    1: 'Summary Advertisement',
    2: 'Subset Advertisements',
    3: 'Advertisement Request',
    4: 'Join'
}


class VTPTimeStampField(StrFixedLenField):
    def __init__(self, name, default):
        StrFixedLenField.__init__(self, name, default, 12)

    def i2repr(self, pkt, x):
        return "%s-%s-%s %s:%s:%s" % (x[:2], x[2:4], x[4:6], x[6:8], x[8:10], x[10:12])  # noqa: E501


class VTP(Packet):
    name = "VTP"
    fields_desc = [
        ByteField("ver", 2),
        ByteEnumField("code", 1, _VTP_Types),
        ConditionalField(ByteField("followers", 1),
                         lambda pkt:pkt.code == 1),
        ConditionalField(ByteField("seq", 1),
                         lambda pkt:pkt.code == 2),
        ConditionalField(ByteField("reserved", 0),
                         lambda pkt:pkt.code == 3),
        ByteField("domnamelen", None),
        StrFixedLenField("domname", "manbearpig", 32),
        ConditionalField(SignedIntField("rev", 0),
                         lambda pkt:pkt.code == 1 or
                         pkt.code == 2),
        # updater identity
        ConditionalField(IPField("uid", "192.168.0.1"),
                         lambda pkt:pkt.code == 1),
        ConditionalField(VTPTimeStampField("timestamp", '930301000000'),
                         lambda pkt:pkt.code == 1),
        ConditionalField(StrFixedLenField("md5", b"\x00" * 16, 16),
                         lambda pkt:pkt.code == 1),
        ConditionalField(
            PacketListField("vlaninfo", [], VTPVlanInfo),
            lambda pkt: pkt.code == 2),
        ConditionalField(ShortField("startvalue", 0),
                         lambda pkt:pkt.code == 3)
    ]

    def post_build(self, p, pay):
        if self.domnamelen is None:
            domnamelen = len(self.domname.strip(b"\x00"))
            p = p[:3] + chr(domnamelen & 0xff) + p[4:]

        p += pay

        return p


bind_layers(SNAP, VTP, code=0x2003)

if __name__ == '__main__':
    from scapy.main import interact
    interact(mydict=globals(), mybanner="VTP")
