1# This file is part of Scapy
2# See http://www.secdev.org/projects/scapy for more informations
3# Copyright (C) Santiago Hernandez Ramos <shramos@protonmail.com>
4# This program is published under GPLv2 license
5
6
7from scapy.packet import Packet, bind_layers
8from scapy.fields import FieldLenField, BitEnumField, StrLenField, \
9    ShortField, ConditionalField, ByteEnumField, ByteField, StrNullField
10from scapy.layers.inet import TCP
11from scapy.error import Scapy_Exception
12from scapy.compat import orb, chb
13
14
15# CUSTOM FIELDS
16
17# source: http://stackoverflow.com/a/43717630
18class VariableFieldLenField(FieldLenField):
19    def addfield(self, pkt, s, val):
20        val = self.i2m(pkt, val)
21        data = []
22        while val:
23            if val > 127:
24                data.append(val & 127)
25                val /= 127
26            else:
27                data.append(val)
28                lastoffset = len(data) - 1
29                data = b"".join(chb(val | (0 if i == lastoffset else 128))
30                               for i, val in enumerate(data))
31                return s + data
32            if len(data) > 3:
33                raise Scapy_Exception("%s: malformed length field" %
34                                      self.__class__.__name__)
35
36    def getfield(self, pkt, s):
37        value = 0
38        for offset, curbyte in enumerate(s):
39            curbyte = orb(curbyte)
40            value += (curbyte & 127) * (128 ** offset)
41            if curbyte & 128 == 0:
42                return s[offset + 1:], value
43            if offset > 2:
44                raise Scapy_Exception("%s: malformed length field" %
45                                      self.__class__.__name__)
46
47
48# LAYERS
49
50CONTROL_PACKET_TYPE = {1: 'CONNECT',
51                       2: 'CONNACK',
52                       3: 'PUBLISH',
53                       4: 'PUBACK',
54                       5: 'PUBREC',
55                       6: 'PUBREL',
56                       7: 'PUBCOMP',
57                       8: 'SUBSCRIBE',
58                       9: 'SUBACK',
59                       10: 'UNSUBSCRIBE',
60                       11: 'UNSUBACK',
61                       12: 'PINGREQ',
62                       13: 'PINGRESP',
63                       14: 'DISCONNECT'}
64
65
66QOS_LEVEL = {0: 'At most once delivery',
67             1: 'At least once delivery',
68             2: 'Exactly once delivery'}
69
70
71# source: http://stackoverflow.com/a/43722441
72class MQTT(Packet):
73    name = "MQTT fixed header"
74    fields_desc = [
75        BitEnumField("type", 1, 4, CONTROL_PACKET_TYPE),
76        BitEnumField("DUP", 0, 1, {0: 'Disabled',
77                                   1: 'Enabled'}),
78        BitEnumField("QOS", 0, 2, QOS_LEVEL),
79        BitEnumField("RETAIN", 0, 1, {0: 'Disabled',
80                                      1: 'Enabled'}),
81        # Since the size of the len field depends on the next layer, we need
82        # to "cheat" with the length_of parameter and use adjust parameter to
83        # calculate the value.
84        VariableFieldLenField("len", None, length_of="len",
85                              adjust=lambda pkt, x: len(pkt.payload),),
86    ]
87
88
89class MQTTConnect(Packet):
90    name = "MQTT connect"
91    fields_desc = [
92        FieldLenField("length", None, length_of="protoname"),
93        StrLenField("protoname", "",
94                    length_from=lambda pkt: pkt.length),
95        ByteField("protolevel", 0),
96        BitEnumField("usernameflag", 0, 1, {0: 'Disabled',
97                                            1: 'Enabled'}),
98        BitEnumField("passwordflag", 0, 1, {0: 'Disabled',
99                                            1: 'Enabled'}),
100        BitEnumField("willretainflag", 0, 1, {0: 'Disabled',
101                                              1: 'Enabled'}),
102        BitEnumField("willQOSflag", 0, 2, QOS_LEVEL),
103        BitEnumField("willflag", 0, 1, {0: 'Disabled',
104                                        1: 'Enabled'}),
105        BitEnumField("cleansess", 0, 1, {0: 'Disabled',
106                                         1: 'Enabled'}),
107        BitEnumField("reserved", 0, 1, {0: 'Disabled',
108                                        1: 'Enabled'}),
109        ShortField("klive", 0),
110        FieldLenField("clientIdlen", None, length_of="clientId"),
111        StrLenField("clientId", "",
112                    length_from=lambda pkt: pkt.clientIdlen),
113        # Payload with optional fields depending on the flags
114        ConditionalField(FieldLenField("wtoplen", None, length_of="willtopic"),
115                         lambda pkt: pkt.willflag == 1),
116        ConditionalField(StrLenField("willtopic", "",
117                                     length_from=lambda pkt: pkt.wtoplen),
118                         lambda pkt: pkt.willflag == 1),
119        ConditionalField(FieldLenField("wmsglen", None, length_of="willmsg"),
120                         lambda pkt: pkt.willflag == 1),
121        ConditionalField(StrLenField("willmsg", "",
122                                     length_from=lambda pkt: pkt.wmsglen),
123                         lambda pkt: pkt.willflag == 1),
124        ConditionalField(FieldLenField("userlen", None, length_of="username"),
125                         lambda pkt: pkt.usernameflag == 1),
126        ConditionalField(StrLenField("username", "",
127                                     length_from=lambda pkt: pkt.userlen),
128                         lambda pkt: pkt.usernameflag == 1),
129        ConditionalField(FieldLenField("passlen", None, length_of="password"),
130                         lambda pkt: pkt.passwordflag == 1),
131        ConditionalField(StrLenField("password", "",
132                                     length_from=lambda pkt: pkt.passlen),
133                         lambda pkt: pkt.passwordflag == 1),
134    ]
135
136
137RETURN_CODE = {0: 'Connection Accepted',
138               1: 'Unacceptable protocol version',
139               2: 'Identifier rejected',
140               3: 'Server unavailable',
141               4: 'Bad username/password',
142               5: 'Not authorized'}
143
144
145class MQTTConnack(Packet):
146    name = "MQTT connack"
147    fields_desc = [
148        ByteField("sessPresentFlag", 0),
149        ByteEnumField("retcode", 0, RETURN_CODE),
150        # this package has not payload
151    ]
152
153
154class MQTTPublish(Packet):
155    name = "MQTT publish"
156    fields_desc = [
157        FieldLenField("length", None, length_of="topic"),
158        StrLenField("topic", "",
159                    length_from=lambda pkt: pkt.length),
160        ConditionalField(ShortField("msgid", None),
161                         lambda pkt: (pkt.underlayer.QOS == 1
162                                      or pkt.underlayer.QOS == 2)),
163        StrLenField("value", "",
164                    length_from=lambda pkt: (pkt.underlayer.len -
165                                             pkt.length - 2)),
166    ]
167
168
169class MQTTPuback(Packet):
170    name = "MQTT puback"
171    fields_desc = [
172        ShortField("msgid", None),
173        ]
174
175
176class MQTTPubrec(Packet):
177    name = "MQTT pubrec"
178    fields_desc = [
179        ShortField("msgid", None),
180        ]
181
182
183class MQTTPubrel(Packet):
184    name = "MQTT pubrel"
185    fields_desc = [
186        ShortField("msgid", None),
187        ]
188
189
190class MQTTPubcomp(Packet):
191    name = "MQTT pubcomp"
192    fields_desc = [
193        ShortField("msgid", None),
194        ]
195
196
197class MQTTSubscribe(Packet):
198    name = "MQTT subscribe"
199    fields_desc = [
200        ShortField("msgid", None),
201        FieldLenField("length", None, length_of="topic"),
202        StrLenField("topic", "",
203                    length_from=lambda pkt: pkt.length),
204        ByteEnumField("QOS", 0, QOS_LEVEL),
205        ]
206
207
208ALLOWED_RETURN_CODE = {0: 'Success',
209                       1: 'Success',
210                       2: 'Success',
211                       128: 'Failure'}
212
213
214class MQTTSuback(Packet):
215    name = "MQTT suback"
216    fields_desc = [
217        ShortField("msgid", None),
218        ByteEnumField("retcode", None, ALLOWED_RETURN_CODE)
219        ]
220
221
222class MQTTUnsubscribe(Packet):
223    name = "MQTT unsubscribe"
224    fields_desc = [
225        ShortField("msgid", None),
226        StrNullField("payload", "")
227        ]
228
229
230class MQTTUnsuback(Packet):
231    name = "MQTT unsuback"
232    fields_desc = [
233        ShortField("msgid", None)
234        ]
235
236
237# LAYERS BINDINGS
238
239bind_layers(TCP, MQTT, sport=1883)
240bind_layers(TCP, MQTT, dport=1883)
241bind_layers(MQTT, MQTTConnect, type=1)
242bind_layers(MQTT, MQTTConnack, type=2)
243bind_layers(MQTT, MQTTPublish, type=3)
244bind_layers(MQTT, MQTTPuback, type=4)
245bind_layers(MQTT, MQTTPubrec, type=5)
246bind_layers(MQTT, MQTTPubrel, type=6)
247bind_layers(MQTT, MQTTPubcomp, type=7)
248bind_layers(MQTT, MQTTSubscribe, type=8)
249bind_layers(MQTT, MQTTSuback, type=9)
250bind_layers(MQTT, MQTTUnsubscribe, type=10)
251bind_layers(MQTT, MQTTUnsuback, type=11)
252bind_layers(MQTTConnect, MQTT)
253bind_layers(MQTTConnack, MQTT)
254bind_layers(MQTTPublish, MQTT)
255bind_layers(MQTTPuback, MQTT)
256bind_layers(MQTTPubrec, MQTT)
257bind_layers(MQTTPubrel, MQTT)
258bind_layers(MQTTPubcomp, MQTT)
259bind_layers(MQTTSubscribe, MQTT)
260bind_layers(MQTTSuback, MQTT)
261bind_layers(MQTTUnsubscribe, MQTT)
262bind_layers(MQTTUnsuback, MQTT)
263