Coverage Report

Created: 2025-07-12 07:37

/src/PcapPlusPlus/Packet++/src/TelnetLayer.cpp
Line
Count
Source (jump to first uncovered line)
1
8.91k
#define LOG_MODULE PacketLogModuleTelnetLayer
2
3
#include "TelnetLayer.h"
4
#include "Logger.h"
5
#include "GeneralUtils.h"
6
#include <cstring>
7
8
namespace pcpp
9
{
10
11
  bool TelnetLayer::isDataField(uint8_t* pos) const
12
129k
  {
13
    // "FF FF" means data
14
129k
    return pos[0] != static_cast<int>(TelnetCommand::InterpretAsCommand) ||
15
129k
           pos[1] == static_cast<int>(TelnetCommand::InterpretAsCommand);
16
129k
  }
17
18
  bool TelnetLayer::isCommandField(uint8_t* pos) const
19
110k
  {
20
110k
    return !isDataField(pos);
21
110k
  }
22
23
  size_t TelnetLayer::distanceToNextIAC(uint8_t* startPos, size_t maxLength)
24
51.7k
  {
25
51.7k
    uint8_t* pos = nullptr;
26
51.7k
    size_t addition = 0;
27
51.7k
    size_t currentOffset = 0;
28
51.7k
    do
29
90.4k
    {
30
      // If it is second turn position should be adjusted to after second FF
31
90.4k
      if (addition)
32
38.7k
        addition += 2;
33
34
90.4k
      pos = (uint8_t*)memchr(startPos + currentOffset + 1, static_cast<int>(TelnetCommand::InterpretAsCommand),
35
90.4k
                             maxLength - currentOffset);
36
90.4k
      if (pos)
37
84.3k
        addition += pos - (startPos + currentOffset);
38
6.13k
      else
39
6.13k
        addition += maxLength - currentOffset;
40
90.4k
      currentOffset = currentOffset + addition;
41
      // "FF FF" means data continue
42
90.4k
    } while (pos && ((pos + 1) < (startPos + maxLength)) &&
43
90.4k
             (pos[1] == static_cast<int>(TelnetCommand::InterpretAsCommand)) && (currentOffset < maxLength));
44
45
51.7k
    return addition;
46
51.7k
  }
47
48
  size_t TelnetLayer::getFieldLen(uint8_t* startPos, size_t maxLength)
49
129k
  {
50
    // Check first byte is IAC
51
129k
    if (startPos && (startPos[0] == static_cast<int>(TelnetCommand::InterpretAsCommand)) && (maxLength >= 2))
52
92.7k
    {
53
      // If subnegotiation parse until next IAC
54
92.7k
      if (startPos[1] == static_cast<int>(TelnetCommand::Subnegotiation))
55
14.5k
        return distanceToNextIAC(startPos, maxLength);
56
      // Only WILL, WONT, DO, DONT have option. Ref http://pcmicro.com/netfoss/telnet.html
57
78.2k
      else if (startPos[1] >= static_cast<int>(TelnetCommand::WillPerform) &&
58
78.2k
               startPos[1] <= static_cast<int>(TelnetCommand::DontPerform))
59
8.27k
        return 3;
60
69.9k
      return 2;
61
92.7k
    }
62
37.1k
    return distanceToNextIAC(startPos, maxLength);
63
129k
  }
64
65
  uint8_t* TelnetLayer::getNextDataField(uint8_t* pos, size_t len)
66
4.12k
  {
67
4.12k
    size_t offset = 0;
68
6.37k
    while (offset < len)
69
6.37k
    {
70
      // Move to next field
71
6.37k
      size_t length = getFieldLen(pos, len - offset);
72
6.37k
      pos += length;
73
6.37k
      offset += length;
74
75
6.37k
      if (isDataField(pos))
76
4.12k
        return pos;
77
6.37k
    }
78
79
0
    return nullptr;
80
4.12k
  }
81
82
  uint8_t* TelnetLayer::getNextCommandField(uint8_t* pos, size_t len)
83
59.5k
  {
84
59.5k
    size_t offset = 0;
85
110k
    while (offset < len)
86
99.6k
    {
87
      // Move to next field
88
99.6k
      size_t length = getFieldLen(pos, len - offset);
89
99.6k
      pos += length;
90
99.6k
      offset += length;
91
92
99.6k
      if ((static_cast<size_t>(pos - m_Data) <= (m_DataLen - 2)) &&
93
99.6k
          isCommandField(pos))  // Need at least 2 bytes for command
94
48.8k
        return pos;
95
99.6k
    }
96
97
10.6k
    return nullptr;
98
59.5k
  }
99
100
  int16_t TelnetLayer::getSubCommand(uint8_t* pos, size_t len)
101
11.9k
  {
102
11.9k
    if (len < 3 || pos[1] < static_cast<int>(TelnetCommand::Subnegotiation))
103
9.11k
      return static_cast<int>(TelnetOption::TelnetOptionNoOption);
104
2.85k
    return pos[2];
105
11.9k
  }
106
107
  uint8_t* TelnetLayer::getCommandData(uint8_t* pos, size_t& len)
108
11.9k
  {
109
11.9k
    if (pos[1] == static_cast<int>(TelnetCommand::Subnegotiation) && len > 3)
110
717
    {
111
717
      len -= 3;
112
717
      return &pos[3];
113
717
    }
114
11.2k
    len = 0;
115
11.2k
    return nullptr;
116
11.9k
  }
117
118
  std::string TelnetLayer::getDataAsString(bool removeEscapeCharacters)
119
8.26k
  {
120
8.26k
    uint8_t* dataPos = nullptr;
121
8.26k
    if (isDataField(m_Data))
122
4.14k
      dataPos = m_Data;
123
4.12k
    else
124
4.12k
      dataPos = getNextDataField(m_Data, m_DataLen);
125
126
8.26k
    if (!dataPos)
127
0
    {
128
0
      PCPP_LOG_DEBUG("Packet does not have a data field");
129
0
      return std::string();
130
0
    }
131
132
    // Convert to string
133
8.26k
    if (removeEscapeCharacters)
134
8.26k
    {
135
8.26k
      std::stringstream ss;
136
171k
      for (size_t idx = 0; idx < m_DataLen - (dataPos - m_Data) + 1; ++idx)
137
163k
      {
138
163k
        if (int(dataPos[idx]) < 127 && int(dataPos[idx]) > 31)  // From SPACE to ~
139
14.3k
          ss << dataPos[idx];
140
163k
      }
141
8.26k
      return ss.str();
142
8.26k
    }
143
0
    return std::string((char*)m_Data, m_DataLen);
144
8.26k
  }
145
146
  size_t TelnetLayer::getTotalNumberOfCommands()
147
2.22k
  {
148
2.22k
    size_t ctr = 0;
149
2.22k
    if (isCommandField(m_Data))
150
907
      ++ctr;
151
152
2.22k
    uint8_t* pos = m_Data;
153
9.58k
    while (pos != nullptr)
154
7.35k
    {
155
7.35k
      size_t offset = pos - m_Data;
156
7.35k
      pos = getNextCommandField(pos, m_DataLen - offset);
157
7.35k
      if (pos)
158
5.12k
        ++ctr;
159
7.35k
    }
160
161
2.22k
    return ctr;
162
2.22k
  }
163
164
  size_t TelnetLayer::getNumberOfCommands(TelnetCommand command)
165
8.26k
  {
166
8.26k
    if (static_cast<int>(command) < 0)
167
2.22k
      return 0;
168
169
6.03k
    size_t ctr = 0;
170
6.03k
    if (isCommandField(m_Data) && m_Data[1] == static_cast<int>(command))
171
1.09k
      ++ctr;
172
173
6.03k
    uint8_t* pos = m_Data;
174
30.4k
    while (pos != nullptr)
175
24.4k
    {
176
24.4k
      size_t offset = pos - m_Data;
177
24.4k
      pos = getNextCommandField(pos, m_DataLen - offset);
178
24.4k
      if (pos && pos[1] == static_cast<int>(command))
179
6.57k
        ++ctr;
180
24.4k
    }
181
182
6.03k
    return ctr;
183
8.26k
  }
184
185
  TelnetLayer::TelnetCommand TelnetLayer::getFirstCommand()
186
2.22k
  {
187
    // If starts with command
188
2.22k
    if (isCommandField(m_Data))
189
907
      return static_cast<TelnetCommand>(m_Data[1]);
190
191
    // Check is there any command
192
1.32k
    uint8_t* pos = getNextCommandField(m_Data, m_DataLen);
193
1.32k
    if (pos)
194
1.12k
      return static_cast<TelnetCommand>(pos[1]);
195
194
    return TelnetCommand::TelnetCommandEndOfPacket;
196
1.32k
  }
197
198
  TelnetLayer::TelnetCommand TelnetLayer::getNextCommand()
199
8.26k
  {
200
8.26k
    if (lastPositionOffset == SIZE_MAX)
201
2.22k
    {
202
2.22k
      lastPositionOffset = 0;
203
2.22k
      if (isCommandField(m_Data))
204
907
        return static_cast<TelnetLayer::TelnetCommand>(m_Data[1]);
205
2.22k
    }
206
207
7.35k
    uint8_t* pos = getNextCommandField(&m_Data[lastPositionOffset], m_DataLen - lastPositionOffset);
208
7.35k
    if (pos)
209
5.12k
    {
210
5.12k
      lastPositionOffset = pos - m_Data;
211
5.12k
      return static_cast<TelnetLayer::TelnetCommand>(pos[1]);
212
5.12k
    }
213
2.22k
    lastPositionOffset = SIZE_MAX;
214
2.22k
    return TelnetCommand::TelnetCommandEndOfPacket;
215
7.35k
  }
216
217
  TelnetLayer::TelnetOption TelnetLayer::getOption()
218
8.26k
  {
219
8.26k
    if (lastPositionOffset < m_DataLen)
220
5.94k
      return static_cast<TelnetOption>(getSubCommand(
221
5.94k
          &m_Data[lastPositionOffset], getFieldLen(&m_Data[lastPositionOffset], m_DataLen - lastPositionOffset)));
222
2.32k
    return TelnetOption::TelnetOptionNoOption;
223
8.26k
  }
224
225
  TelnetLayer::TelnetOption TelnetLayer::getOption(TelnetCommand command)
226
8.26k
  {
227
    // Check input
228
8.26k
    if (static_cast<int>(command) < 0)
229
2.22k
    {
230
2.22k
      PCPP_LOG_ERROR("Command type can't be negative");
231
2.22k
      return TelnetOption::TelnetOptionNoOption;
232
2.22k
    }
233
234
6.03k
    if (isCommandField(m_Data) && m_Data[1] == static_cast<int>(command))
235
1.09k
      return static_cast<TelnetOption>(getSubCommand(m_Data, getFieldLen(m_Data, m_DataLen)));
236
237
4.94k
    uint8_t* pos = m_Data;
238
9.52k
    while (pos != nullptr)
239
9.52k
    {
240
9.52k
      size_t offset = pos - m_Data;
241
9.52k
      pos = getNextCommandField(pos, m_DataLen - offset);
242
243
9.52k
      if (pos && pos[1] == static_cast<int>(command))
244
4.94k
        return static_cast<TelnetOption>(getSubCommand(pos, getFieldLen(pos, m_DataLen - offset)));
245
9.52k
    }
246
247
0
    PCPP_LOG_DEBUG("Can't find requested command");
248
0
    return TelnetOption::TelnetOptionNoOption;
249
4.94k
  }
250
251
  uint8_t* TelnetLayer::getOptionData(size_t& length)
252
8.26k
  {
253
8.26k
    if (lastPositionOffset < m_DataLen)
254
5.94k
    {
255
5.94k
      size_t lenBuffer = getFieldLen(&m_Data[lastPositionOffset], m_DataLen - lastPositionOffset);
256
5.94k
      uint8_t* posBuffer = getCommandData(&m_Data[lastPositionOffset], lenBuffer);
257
258
5.94k
      length = lenBuffer;
259
5.94k
      return posBuffer;
260
5.94k
    }
261
2.32k
    return nullptr;
262
8.26k
  }
263
264
  uint8_t* TelnetLayer::getOptionData(TelnetCommand command, size_t& length)
265
8.26k
  {
266
    // Check input
267
8.26k
    if (static_cast<int>(command) < 0)
268
2.22k
    {
269
2.22k
      PCPP_LOG_ERROR("Command type can't be negative");
270
2.22k
      length = 0;
271
2.22k
      return nullptr;
272
2.22k
    }
273
274
6.03k
    if (isCommandField(m_Data) && m_Data[1] == static_cast<int>(command))
275
1.09k
    {
276
1.09k
      size_t lenBuffer = getFieldLen(m_Data, m_DataLen);
277
1.09k
      uint8_t* posBuffer = getCommandData(m_Data, lenBuffer);
278
279
1.09k
      length = lenBuffer;
280
1.09k
      return posBuffer;
281
1.09k
    }
282
283
4.94k
    uint8_t* pos = m_Data;
284
9.52k
    while (pos != nullptr)
285
9.52k
    {
286
9.52k
      size_t offset = pos - m_Data;
287
9.52k
      pos = getNextCommandField(pos, m_DataLen - offset);
288
289
9.52k
      if (pos && pos[1] == static_cast<int>(command))
290
4.94k
      {
291
4.94k
        size_t lenBuffer = getFieldLen(m_Data, m_DataLen);
292
4.94k
        uint8_t* posBuffer = getCommandData(m_Data, lenBuffer);
293
294
4.94k
        length = lenBuffer;
295
4.94k
        return posBuffer;
296
4.94k
      }
297
9.52k
    }
298
299
0
    PCPP_LOG_DEBUG("Can't find requested command");
300
0
    length = 0;
301
0
    return nullptr;
302
4.94k
  }
303
304
  std::string TelnetLayer::getTelnetCommandAsString(TelnetCommand val)
305
8.26k
  {
306
8.26k
    switch (val)
307
8.26k
    {
308
2.22k
    case TelnetCommand::TelnetCommandEndOfPacket:
309
2.22k
      return "Reached end of packet while parsing";
310
36
    case TelnetCommand::EndOfFile:
311
36
      return "End of File";
312
0
    case TelnetCommand::Suspend:
313
0
      return "Suspend current process";
314
68
    case TelnetCommand::Abort:
315
68
      return "Abort Process";
316
146
    case TelnetCommand::EndOfRecordCommand:
317
146
      return "End of Record";
318
880
    case TelnetCommand::SubnegotiationEnd:
319
880
      return "Subnegotiation End";
320
4
    case TelnetCommand::NoOperation:
321
4
      return "No Operation";
322
48
    case TelnetCommand::DataMark:
323
48
      return "Data Mark";
324
58
    case TelnetCommand::Break:
325
58
      return "Break";
326
387
    case TelnetCommand::InterruptProcess:
327
387
      return "Interrupt Process";
328
22
    case TelnetCommand::AbortOutput:
329
22
      return "Abort Output";
330
1
    case TelnetCommand::AreYouThere:
331
1
      return "Are You There";
332
161
    case TelnetCommand::EraseCharacter:
333
161
      return "Erase Character";
334
32
    case TelnetCommand::EraseLine:
335
32
      return "Erase Line";
336
174
    case TelnetCommand::GoAhead:
337
174
      return "Go Ahead";
338
962
    case TelnetCommand::Subnegotiation:
339
962
      return "Subnegotiation";
340
64
    case TelnetCommand::WillPerform:
341
64
      return "Will Perform";
342
192
    case TelnetCommand::WontPerform:
343
192
      return "Wont Perform";
344
128
    case TelnetCommand::DoPerform:
345
128
      return "Do Perform";
346
427
    case TelnetCommand::DontPerform:
347
427
      return "Dont Perform";
348
0
    case TelnetCommand::InterpretAsCommand:
349
0
      return "Interpret As Command";
350
2.24k
    default:
351
2.24k
      return "Unknown Command";
352
8.26k
    }
353
8.26k
  }
354
355
  std::string TelnetLayer::getTelnetOptionAsString(TelnetOption val)
356
8.26k
  {
357
8.26k
    switch (val)
358
8.26k
    {
359
6.83k
    case TelnetOption::TelnetOptionNoOption:
360
6.83k
      return "No option for this command";
361
0
    case TelnetOption::TransmitBinary:
362
0
      return "Binary Transmission";
363
0
    case TelnetOption::Echo:
364
0
      return "Echo";
365
0
    case TelnetOption::Reconnection:
366
0
      return "Reconnection";
367
0
    case TelnetOption::SuppressGoAhead:
368
0
      return "Suppress Go Ahead";
369
0
    case TelnetOption::ApproxMsgSizeNegotiation:
370
0
      return "Negotiate approximate message size";
371
0
    case TelnetOption::Status:
372
0
      return "Status";
373
0
    case TelnetOption::TimingMark:
374
0
      return "Timing Mark";
375
32
    case TelnetOption::RemoteControlledTransAndEcho:
376
32
      return "Remote Controlled Transmission and Echo";
377
0
    case TelnetOption::OutputLineWidth:
378
0
      return "Output Line Width";
379
0
    case TelnetOption::OutputPageSize:
380
0
      return "Output Page Size";
381
0
    case TelnetOption::OutputCarriageReturnDisposition:
382
0
      return "Negotiate About Output Carriage-Return Disposition";
383
0
    case TelnetOption::OutputHorizontalTabStops:
384
0
      return "Negotiate About Output Horizontal Tabstops";
385
0
    case TelnetOption::OutputHorizontalTabDisposition:
386
0
      return "Negotiate About Output Horizontal Tab Disposition";
387
0
    case TelnetOption::OutputFormfeedDisposition:
388
0
      return "Negotiate About Output Formfeed Disposition";
389
0
    case TelnetOption::OutputVerticalTabStops:
390
0
      return "Negotiate About Vertical Tabstops";
391
0
    case TelnetOption::OutputVerticalTabDisposition:
392
0
      return "Negotiate About Output Vertcial Tab Disposition";
393
0
    case TelnetOption::OutputLinefeedDisposition:
394
0
      return "Negotiate About Output Linefeed Disposition";
395
0
    case TelnetOption::ExtendedASCII:
396
0
      return "Extended ASCII";
397
24
    case TelnetOption::Logout:
398
24
      return "Logout";
399
32
    case TelnetOption::ByteMacro:
400
32
      return "Byte Macro";
401
0
    case TelnetOption::DataEntryTerminal:
402
0
      return "Data Entry Terminal";
403
0
    case TelnetOption::SUPDUP:
404
0
      return "SUPDUP";
405
32
    case TelnetOption::SUPDUPOutput:
406
32
      return "SUPDUP Output";
407
32
    case TelnetOption::SendLocation:
408
32
      return "Send Location";
409
0
    case TelnetOption::TerminalType:
410
0
      return "Terminal Type";
411
0
    case TelnetOption::EndOfRecordOption:
412
0
      return "End Of Record";
413
0
    case TelnetOption::TACACSUserIdentification:
414
0
      return "TACACS User Identification";
415
0
    case TelnetOption::OutputMarking:
416
0
      return "Output Marking";
417
0
    case TelnetOption::TerminalLocationNumber:
418
0
      return "Terminal Location Number";
419
2
    case TelnetOption::Telnet3270Regime:
420
2
      return "Telnet 3270 Regime";
421
24
    case TelnetOption::X3Pad:
422
24
      return "X3 Pad";
423
0
    case TelnetOption::NegotiateAboutWindowSize:
424
0
      return "Negotiate About Window Size";
425
96
    case TelnetOption::TerminalSpeed:
426
96
      return "Terminal Speed";
427
96
    case TelnetOption::RemoteFlowControl:
428
96
      return "Remote Flow Control";
429
0
    case TelnetOption::Linemode:
430
0
      return "Line mode";
431
0
    case TelnetOption::XDisplayLocation:
432
0
      return "X Display Location";
433
0
    case TelnetOption::EnvironmentOption:
434
0
      return "Environment Option";
435
65
    case TelnetOption::AuthenticationOption:
436
65
      return "Authentication Option";
437
128
    case TelnetOption::EncryptionOption:
438
128
      return "Encryption Option";
439
0
    case TelnetOption::NewEnvironmentOption:
440
0
      return "New Environment Option";
441
0
    case TelnetOption::TN3270E:
442
0
      return "TN3270E";
443
0
    case TelnetOption::XAuth:
444
0
      return "X Server Authentication";
445
0
    case TelnetOption::Charset:
446
0
      return "Charset";
447
0
    case TelnetOption::TelnetRemoteSerialPort:
448
0
      return "Telnet Remote Serial Port";
449
22
    case TelnetOption::ComPortControlOption:
450
22
      return "Com Port Control Option";
451
0
    case TelnetOption::TelnetSuppressLocalEcho:
452
0
      return "Telnet Suppress Local Echo";
453
0
    case TelnetOption::TelnetStartTLS:
454
0
      return "Telnet Start TLS";
455
0
    case TelnetOption::Kermit:
456
0
      return "Kermit";
457
0
    case TelnetOption::SendURL:
458
0
      return "Send URL";
459
0
    case TelnetOption::ForwardX:
460
0
      return "Forward X Server";
461
0
    case TelnetOption::TelOptPragmaLogon:
462
0
      return "Telnet Option Pragma Logon";
463
130
    case TelnetOption::TelOptSSPILogon:
464
130
      return "Telnet Option SSPI Logon";
465
33
    case TelnetOption::TelOptPragmaHeartbeat:
466
33
      return "Telnet Option Pragma Heartbeat";
467
655
    case TelnetOption::ExtendedOptions:
468
655
      return "Extended option list";
469
26
    default:
470
26
      return "Unknown Option";
471
8.26k
    }
472
8.26k
  }
473
474
  std::string TelnetLayer::toString() const
475
4.45k
  {
476
4.45k
    if (isDataField(m_Data))
477
2.64k
      return "Telnet Data";
478
1.81k
    return "Telnet Control";
479
4.45k
  }
480
481
}  // namespace pcpp