1# $Id: netbios.py 23 2006-11-08 15:45:33Z dugsong $
2# -*- coding: utf-8 -*-
3"""Network Basic Input/Output System."""
4from __future__ import absolute_import
5
6import struct
7
8from . import dpkt
9from . import dns
10from .compat import compat_ord
11
12
13def encode_name(name):
14    """
15    Return the NetBIOS first-level encoded name.
16
17    14.1.  FIRST LEVEL ENCODING
18
19    The first level representation consists of two parts:
20
21     -  NetBIOS name
22     -  NetBIOS scope identifier
23
24    The 16 byte NetBIOS name is mapped into a 32 byte wide field using a
25    reversible, half-ASCII, biased encoding.  Each half-octet of the
26    NetBIOS name is encoded into one byte of the 32 byte field.  The
27    first half octet is encoded into the first byte, the second half-
28    octet into the second byte, etc.
29
30    Each 4-bit, half-octet of the NetBIOS name is treated as an 8-bit,
31    right-adjusted, zero-filled binary number.  This number is added to
32    value of the ASCII character 'A' (hexidecimal 41).  The resulting 8-
33    bit number is stored in the appropriate byte.  The following diagram
34    demonstrates this procedure:
35
36
37                         0 1 2 3 4 5 6 7
38                        +-+-+-+-+-+-+-+-+
39                        |a b c d|w x y z|          ORIGINAL BYTE
40                        +-+-+-+-+-+-+-+-+
41                            |       |
42                   +--------+       +--------+
43                   |                         |     SPLIT THE NIBBLES
44                   v                         v
45            0 1 2 3 4 5 6 7           0 1 2 3 4 5 6 7
46           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
47           |0 0 0 0 a b c d|         |0 0 0 0 w x y z|
48           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
49                   |                         |
50                   +                         +     ADD 'A'
51                   |                         |
52            0 1 2 3 4 5 6 7           0 1 2 3 4 5 6 7
53           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
54           |0 1 0 0 0 0 0 1|         |0 1 0 0 0 0 0 1|
55           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
56
57    This encoding results in a NetBIOS name being represented as a
58    sequence of 32 ASCII, upper-case characters from the set
59    {A,B,C...N,O,P}.
60
61    The NetBIOS scope identifier is a valid domain name (without a
62    leading dot).
63
64    An ASCII dot (2E hexidecimal) and the scope identifier are appended
65    to the encoded form of the NetBIOS name, the result forming a valid
66    domain name.
67    """
68    l_ = []
69    for c in struct.pack('16s', name.encode()):
70        c = compat_ord(c)
71        l_.append(chr((c >> 4) + 0x41))
72        l_.append(chr((c & 0xf) + 0x41))
73    return ''.join(l_)
74
75
76def decode_name(nbname):
77    """
78    Return the NetBIOS first-level decoded nbname.
79
80    """
81    if len(nbname) != 32:
82        return nbname
83
84    l_ = []
85    for i in range(0, 32, 2):
86        l_.append(
87            chr(
88                ((ord(nbname[i]) - 0x41) << 4) |
89                ((ord(nbname[i + 1]) - 0x41) & 0xf)
90            )
91        )
92    return ''.join(l_).split('\x00', 1)[0]
93
94
95# RR types
96NS_A = 0x01  # IP address
97NS_NS = 0x02  # Name Server
98NS_NULL = 0x0A  # NULL
99NS_NB = 0x20  # NetBIOS general Name Service
100NS_NBSTAT = 0x21  # NetBIOS NODE STATUS
101
102# RR classes
103NS_IN = 1
104
105# NBSTAT name flags
106NS_NAME_G = 0x8000  # group name (as opposed to unique)
107NS_NAME_DRG = 0x1000  # deregister
108NS_NAME_CNF = 0x0800  # conflict
109NS_NAME_ACT = 0x0400  # active
110NS_NAME_PRM = 0x0200  # permanent
111
112# NBSTAT service names
113nbstat_svcs = {
114    # (service, unique): list of ordered (name prefix, service name) tuples
115    (0x00, 0): [('', 'Domain Name')],
116    (0x00, 1): [('IS~', 'IIS'), ('', 'Workstation Service')],
117    (0x01, 0): [('__MSBROWSE__', 'Master Browser')],
118    (0x01, 1): [('', 'Messenger Service')],
119    (0x03, 1): [('', 'Messenger Service')],
120    (0x06, 1): [('', 'RAS Server Service')],
121    (0x1B, 1): [('', 'Domain Master Browser')],
122    (0x1C, 0): [('INet~Services', 'IIS'), ('', 'Domain Controllers')],
123    (0x1D, 1): [('', 'Master Browser')],
124    (0x1E, 0): [('', 'Browser Service Elections')],
125    (0x1F, 1): [('', 'NetDDE Service')],
126    (0x20, 1): [('Forte_$ND800ZA', 'DCA IrmaLan Gateway Server Service'),
127                ('', 'File Server Service')],
128    (0x21, 1): [('', 'RAS Client Service')],
129    (0x22, 1): [('', 'Microsoft Exchange Interchange(MSMail Connector)')],
130    (0x23, 1): [('', 'Microsoft Exchange Store')],
131    (0x24, 1): [('', 'Microsoft Exchange Directory')],
132    (0x2B, 1): [('', 'Lotus Notes Server Service')],
133    (0x2F, 0): [('IRISMULTICAST', 'Lotus Notes')],
134    (0x30, 1): [('', 'Modem Sharing Server Service')],
135    (0x31, 1): [('', 'Modem Sharing Client Service')],
136    (0x33, 0): [('IRISNAMESERVER', 'Lotus Notes')],
137    (0x43, 1): [('', 'SMS Clients Remote Control')],
138    (0x44, 1): [('', 'SMS Administrators Remote Control Tool')],
139    (0x45, 1): [('', 'SMS Clients Remote Chat')],
140    (0x46, 1): [('', 'SMS Clients Remote Transfer')],
141    (0x4C, 1): [('', 'DEC Pathworks TCPIP service on Windows NT')],
142    (0x52, 1): [('', 'DEC Pathworks TCPIP service on Windows NT')],
143    (0x87, 1): [('', 'Microsoft Exchange MTA')],
144    (0x6A, 1): [('', 'Microsoft Exchange IMC')],
145    (0xBE, 1): [('', 'Network Monitor Agent')],
146    (0xBF, 1): [('', 'Network Monitor Application')]
147}
148
149
150def node_to_service_name(name_service_flags):
151    name, service, flags = name_service_flags
152    try:
153        unique = int(flags & NS_NAME_G == 0)
154        for namepfx, svcname in nbstat_svcs[(service, unique)]:
155            if name.startswith(namepfx):
156                return svcname
157    except KeyError:
158        pass
159    return ''
160
161
162class NS(dns.DNS):
163    """
164    NetBIOS Name Service.
165
166    RFC1002: https://tools.ietf.org/html/rfc1002
167    """
168
169    class Q(dns.DNS.Q):
170        pass
171
172    class RR(dns.DNS.RR):
173        """NetBIOS resource record.
174
175        RFC1001: 14.  REPRESENTATION OF NETBIOS NAMES
176
177        NetBIOS names as seen across the client interface to NetBIOS are
178        exactly 16 bytes long.  Within the NetBIOS-over-TCP protocols, a
179        longer representation is used.
180
181        There are two levels of encoding.  The first level maps a NetBIOS
182        name into a domain system name.  The second level maps the domain
183        system name into the "compressed" representation required for
184        interaction with the domain name system.
185
186        Except in one packet, the second level representation is the only
187        NetBIOS name representation used in NetBIOS-over-TCP packet formats.
188        The exception is the RDATA field of a NODE STATUS RESPONSE packet.
189        """
190
191        _node_name_struct = struct.Struct('>15s B H')
192        _node_name_len = _node_name_struct.size
193
194        def unpack_rdata(self, buf, off):
195            if self.type == NS_A:
196                self.ip = self.rdata
197            elif self.type == NS_NBSTAT:
198                num_names = compat_ord(self.rdata[0])
199
200                self.nodenames = [
201                    self._node_name_struct.unpack_from(
202                        self.rdata, 1+idx*self._node_name_len
203                    ) for idx in range(num_names)
204                ]
205                # XXX - skip stats
206
207
208class Session(dpkt.Packet):
209    """NetBIOS Session Service."""
210    __hdr__ = (
211        ('type', 'B', 0),
212        ('flags', 'B', 0),
213        ('len', 'H', 0)
214    )
215
216
217SSN_MESSAGE = 0
218SSN_REQUEST = 1
219SSN_POSITIVE = 2
220SSN_NEGATIVE = 3
221SSN_RETARGET = 4
222SSN_KEEPALIVE = 5
223
224
225class Datagram(dpkt.Packet):
226    """NetBIOS Datagram Service."""
227    __hdr__ = (
228        ('type', 'B', 0),
229        ('flags', 'B', 0),
230        ('id', 'H', 0),
231        ('src', 'I', 0),
232        ('sport', 'H', 0),
233        ('len', 'H', 0),
234        ('off', 'H', 0)
235    )
236
237
238DGRAM_UNIQUE = 0x10
239DGRAM_GROUP = 0x11
240DGRAM_BROADCAST = 0x12
241DGRAM_ERROR = 0x13
242DGRAM_QUERY = 0x14
243DGRAM_POSITIVE = 0x15
244DGRAM_NEGATIVE = 0x16
245
246
247def test_encode_name():
248    assert encode_name('The NetBIOS name') == 'FEGIGFCAEOGFHEECEJEPFDCAGOGBGNGF'
249    # rfc1002
250    assert encode_name('FRED            ') == 'EGFCEFEECACACACACACACACACACACACA'
251    # https://github.com/kbandla/dpkt/issues/458
252    assert encode_name('*') == 'CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
253
254
255def test_decode_name():
256    assert decode_name('FEGIGFCAEOGFHEECEJEPFDCAGOGBGNGF') == 'The NetBIOS name'
257    # original botched example from rfc1001
258    assert decode_name('FEGHGFCAEOGFHEECEJEPFDCAHEGBGNGF') == 'Tge NetBIOS tame'
259    assert decode_name('CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') == '*'
260
261    # decode a name which is not 32 chars long
262    assert decode_name('CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB') == 'CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB'
263
264
265def test_node_to_service_name():
266    svcname = node_to_service_name(("ISS", 0x00, 0x0800))
267    assert svcname == "Workstation Service"
268
269
270def test_node_to_service_name_keyerror():
271    svcname = node_to_service_name(("ISS", 0xff, 0x0800))
272    assert svcname == ""
273
274
275def test_rr():
276    import pytest
277    from binascii import unhexlify
278
279    rr = NS.RR()
280    with pytest.raises(NotImplementedError):
281        len(rr)
282
283    buf = unhexlify(''.join([
284        '01',        # A record
285        '0001',      # DNS_IN
286        '00000000',  # TTL
287        '0000',      # rlen
288    ]))
289    rr.unpack_rdata(buf, 0)
290    assert rr.ip == rr.rdata
291
292
293def test_rr_nbstat():
294    from binascii import unhexlify
295    buf = unhexlify(''.join([
296        '41' * 1025,  # Name
297        '0033',       # NS_NBSTAT
298        '0001',       # DNS_IN
299        '00000000',   # TTL
300        '0004',       # rlen
301    ]))
302    rdata = (
303        b'\x02'  # NUM_NAMES
304        b'ABCDEFGHIJKLMNO\x2f\x01\x02'
305        b'PQRSTUVWXYZABCD\x43\x03\x04'
306    )
307    rr = NS.RR(
308        type=NS_NBSTAT,
309        rdata=rdata,
310    )
311
312    assert rr.type == NS_NBSTAT
313    rr.unpack_rdata(buf, 0)
314    assert rr.nodenames == [
315        (b'ABCDEFGHIJKLMNO', 0x2f, 0x0102),
316        (b'PQRSTUVWXYZABCD', 0x43, 0x0304),
317    ]
318
319
320def test_ns():
321    from binascii import unhexlify
322    ns = NS()
323    correct = unhexlify(
324        '0000'
325        '0100'
326        '0000000000000000'
327    )
328    assert bytes(ns) == correct
329