logparse.pl 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330
  1. #!/usr/bin/perl
  2. use Getopt::Long;
  3. use strict;
  4. use warnings;
  5. use FileHandle;
  6. my $dumpchannels = 0;
  7. my $dumpdata = 0;
  8. my $pass_through_events = 0;
  9. my $verbose_all;
  10. my %verbose_packet;
  11. GetOptions("dump-channels|c" => \$dumpchannels,
  12. "dump-data|d" => \$dumpdata,
  13. "verbose|v" => \$verbose_all,
  14. "full|f=s" => sub { $verbose_packet{$_[1]} = 1; },
  15. "events|e" => \$pass_through_events,
  16. "help" => sub { &usage(\*STDOUT, 0); })
  17. or &usage(\*STDERR, 1);
  18. sub usage {
  19. my ($fh, $exitstatus) = @_;
  20. print $fh <<'EOF';
  21. usage: logparse.pl [ options ] [ input-log-file ]
  22. options: --dump-channels, -c dump the final state of every channel
  23. --dump-data, -d save data of every channel to ch0.i, ch0.o, ...
  24. --full=PKT, -f PKT print extra detail for packets of type PKT
  25. --verbose, -v print extra detail for all packets if available
  26. --events, -e copy Event Log messages from input log file
  27. EOF
  28. exit $exitstatus;
  29. }
  30. my @channels = (); # ultimate channel ids are indices in this array
  31. my %chan_by_id = (); # indexed by 'c%d' or 's%d' for client and server ids
  32. my %globalreq = (); # indexed by 'i' or 'o'
  33. my %packets = (
  34. #define SSH2_MSG_DISCONNECT 1 /* 0x1 */
  35. 'SSH2_MSG_DISCONNECT' => sub {
  36. my ($direction, $seq, $data) = @_;
  37. my ($reason, $description, $lang) = &parse("uss", $data);
  38. printf "%s\n", &str($description);
  39. },
  40. #define SSH2_MSG_IGNORE 2 /* 0x2 */
  41. 'SSH2_MSG_IGNORE' => sub {
  42. my ($direction, $seq, $data) = @_;
  43. my ($str) = &parse("s", $data);
  44. printf "(%d bytes)\n", length $str;
  45. },
  46. #define SSH2_MSG_UNIMPLEMENTED 3 /* 0x3 */
  47. 'SSH2_MSG_UNIMPLEMENTED' => sub {
  48. my ($direction, $seq, $data) = @_;
  49. my ($rseq) = &parse("u", $data);
  50. printf "i%d\n", $rseq;
  51. },
  52. #define SSH2_MSG_DEBUG 4 /* 0x4 */
  53. 'SSH2_MSG_DEBUG' => sub {
  54. my ($direction, $seq, $data) = @_;
  55. my ($disp, $message, $lang) = &parse("bss", $data);
  56. printf "%s\n", &str($message);
  57. },
  58. #define SSH2_MSG_SERVICE_REQUEST 5 /* 0x5 */
  59. 'SSH2_MSG_SERVICE_REQUEST' => sub {
  60. my ($direction, $seq, $data) = @_;
  61. my ($service) = &parse("s", $data);
  62. printf "%s\n", &str($service);
  63. },
  64. #define SSH2_MSG_SERVICE_ACCEPT 6 /* 0x6 */
  65. 'SSH2_MSG_SERVICE_ACCEPT' => sub {
  66. my ($direction, $seq, $data) = @_;
  67. my ($service) = &parse("s", $data);
  68. printf "%s\n", &str($service);
  69. },
  70. #define SSH2_MSG_KEXINIT 20 /* 0x14 */
  71. 'SSH2_MSG_KEXINIT' => sub {
  72. my ($direction, $seq, $data) = @_;
  73. print "\n";
  74. },
  75. #define SSH2_MSG_NEWKEYS 21 /* 0x15 */
  76. 'SSH2_MSG_NEWKEYS' => sub {
  77. my ($direction, $seq, $data) = @_;
  78. print "\n";
  79. },
  80. #define SSH2_MSG_KEXDH_INIT 30 /* 0x1e */
  81. 'SSH2_MSG_KEXDH_INIT' => sub {
  82. my ($direction, $seq, $data) = @_;
  83. print "\n";
  84. },
  85. #define SSH2_MSG_KEXDH_REPLY 31 /* 0x1f */
  86. 'SSH2_MSG_KEXDH_REPLY' => sub {
  87. my ($direction, $seq, $data) = @_;
  88. print "\n";
  89. },
  90. #define SSH2_MSG_KEX_DH_GEX_REQUEST 30 /* 0x1e */
  91. 'SSH2_MSG_KEX_DH_GEX_REQUEST' => sub {
  92. my ($direction, $seq, $data) = @_;
  93. print "\n";
  94. },
  95. #define SSH2_MSG_KEX_DH_GEX_GROUP 31 /* 0x1f */
  96. 'SSH2_MSG_KEX_DH_GEX_GROUP' => sub {
  97. my ($direction, $seq, $data) = @_;
  98. print "\n";
  99. },
  100. #define SSH2_MSG_KEX_DH_GEX_INIT 32 /* 0x20 */
  101. 'SSH2_MSG_KEX_DH_GEX_INIT' => sub {
  102. my ($direction, $seq, $data) = @_;
  103. print "\n";
  104. },
  105. #define SSH2_MSG_KEX_DH_GEX_REPLY 33 /* 0x21 */
  106. 'SSH2_MSG_KEX_DH_GEX_REPLY' => sub {
  107. my ($direction, $seq, $data) = @_;
  108. print "\n";
  109. },
  110. #define SSH2_MSG_KEXGSS_INIT 30 /* 0x1e */
  111. 'SSH2_MSG_KEXGSS_INIT' => sub {
  112. my ($direction, $seq, $data) = @_;
  113. print "\n";
  114. },
  115. #define SSH2_MSG_KEXGSS_CONTINUE 31 /* 0x1f */
  116. 'SSH2_MSG_KEXGSS_CONTINUE' => sub {
  117. my ($direction, $seq, $data) = @_;
  118. print "\n";
  119. },
  120. #define SSH2_MSG_KEXGSS_COMPLETE 32 /* 0x20 */
  121. 'SSH2_MSG_KEXGSS_COMPLETE' => sub {
  122. my ($direction, $seq, $data) = @_;
  123. print "\n";
  124. },
  125. #define SSH2_MSG_KEXGSS_HOSTKEY 33 /* 0x21 */
  126. 'SSH2_MSG_KEXGSS_HOSTKEY' => sub {
  127. my ($direction, $seq, $data) = @_;
  128. print "\n";
  129. },
  130. #define SSH2_MSG_KEXGSS_ERROR 34 /* 0x22 */
  131. 'SSH2_MSG_KEXGSS_ERROR' => sub {
  132. my ($direction, $seq, $data) = @_;
  133. print "\n";
  134. },
  135. #define SSH2_MSG_KEXGSS_GROUPREQ 40 /* 0x28 */
  136. 'SSH2_MSG_KEXGSS_GROUPREQ' => sub {
  137. my ($direction, $seq, $data) = @_;
  138. print "\n";
  139. },
  140. #define SSH2_MSG_KEXGSS_GROUP 41 /* 0x29 */
  141. 'SSH2_MSG_KEXGSS_GROUP' => sub {
  142. my ($direction, $seq, $data) = @_;
  143. print "\n";
  144. },
  145. #define SSH2_MSG_KEXRSA_PUBKEY 30 /* 0x1e */
  146. 'SSH2_MSG_KEXRSA_PUBKEY' => sub {
  147. my ($direction, $seq, $data) = @_;
  148. print "\n";
  149. },
  150. #define SSH2_MSG_KEXRSA_SECRET 31 /* 0x1f */
  151. 'SSH2_MSG_KEXRSA_SECRET' => sub {
  152. my ($direction, $seq, $data) = @_;
  153. print "\n";
  154. },
  155. #define SSH2_MSG_KEXRSA_DONE 32 /* 0x20 */
  156. 'SSH2_MSG_KEXRSA_DONE' => sub {
  157. my ($direction, $seq, $data) = @_;
  158. print "\n";
  159. },
  160. #define SSH2_MSG_KEX_ECDH_INIT 30 /* 0x1e */
  161. 'SSH2_MSG_KEX_ECDH_INIT' => sub {
  162. my ($direction, $seq, $data) = @_;
  163. print "\n";
  164. },
  165. #define SSH2_MSG_KEX_ECDH_REPLY 31 /* 0x1f */
  166. 'SSH2_MSG_KEX_ECDH_REPLY' => sub {
  167. my ($direction, $seq, $data) = @_;
  168. print "\n";
  169. },
  170. #define SSH2_MSG_USERAUTH_REQUEST 50 /* 0x32 */
  171. 'SSH2_MSG_USERAUTH_REQUEST' => sub {
  172. my ($direction, $seq, $data) = @_;
  173. my ($user, $service, $method) = &parse("sss", $data);
  174. my $out = sprintf "%s %s %s",
  175. &str($user), &str($service), &str($method);
  176. if ($method eq "publickey") {
  177. my ($real) = &parse("b", $data);
  178. $out .= " real=$real";
  179. } elsif ($method eq "password") {
  180. my ($change) = &parse("b", $data);
  181. $out .= " change=$change";
  182. }
  183. print "$out\n";
  184. },
  185. #define SSH2_MSG_USERAUTH_FAILURE 51 /* 0x33 */
  186. 'SSH2_MSG_USERAUTH_FAILURE' => sub {
  187. my ($direction, $seq, $data) = @_;
  188. my ($options) = &parse("s", $data);
  189. printf "%s\n", &str($options);
  190. },
  191. #define SSH2_MSG_USERAUTH_SUCCESS 52 /* 0x34 */
  192. 'SSH2_MSG_USERAUTH_SUCCESS' => sub {
  193. my ($direction, $seq, $data) = @_;
  194. print "\n";
  195. },
  196. #define SSH2_MSG_USERAUTH_BANNER 53 /* 0x35 */
  197. 'SSH2_MSG_USERAUTH_BANNER' => sub {
  198. my ($direction, $seq, $data) = @_;
  199. print "\n";
  200. },
  201. #define SSH2_MSG_USERAUTH_PK_OK 60 /* 0x3c */
  202. 'SSH2_MSG_USERAUTH_PK_OK' => sub {
  203. my ($direction, $seq, $data) = @_;
  204. print "\n";
  205. },
  206. #define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 /* 0x3c */
  207. 'SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ' => sub {
  208. my ($direction, $seq, $data) = @_;
  209. print "\n";
  210. },
  211. #define SSH2_MSG_USERAUTH_INFO_REQUEST 60 /* 0x3c */
  212. 'SSH2_MSG_USERAUTH_INFO_REQUEST' => sub {
  213. my ($direction, $seq, $data) = @_;
  214. print "\n";
  215. },
  216. #define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 /* 0x3d */
  217. 'SSH2_MSG_USERAUTH_INFO_RESPONSE' => sub {
  218. my ($direction, $seq, $data) = @_;
  219. print "\n";
  220. },
  221. #define SSH2_MSG_GLOBAL_REQUEST 80 /* 0x50 */
  222. 'SSH2_MSG_GLOBAL_REQUEST' => sub {
  223. my ($direction, $seq, $data) = @_;
  224. my ($type, $wantreply) = &parse("sb", $data);
  225. printf "%s (%s)", $type, $wantreply eq "yes" ? "reply" : "noreply";
  226. my $request = [$seq, $type];
  227. push @{$globalreq{$direction}}, $request if $wantreply eq "yes";
  228. if ($type eq "tcpip-forward" or $type eq "cancel-tcpip-forward") {
  229. my ($addr, $port) = &parse("su", $data);
  230. printf " %s:%s", $addr, $port;
  231. push @$request, $port;
  232. }
  233. print "\n";
  234. },
  235. #define SSH2_MSG_REQUEST_SUCCESS 81 /* 0x51 */
  236. 'SSH2_MSG_REQUEST_SUCCESS' => sub {
  237. my ($direction, $seq, $data) = @_;
  238. my $otherdir = ($direction eq "i" ? "o" : "i");
  239. my $request = shift @{$globalreq{$otherdir}};
  240. if (defined $request) {
  241. printf "to %s", $request->[0];
  242. if ($request->[1] eq "tcpip-forward" and $request->[2] == 0) {
  243. my ($port) = &parse("u", $data);
  244. printf " port=%s", $port;
  245. }
  246. } else {
  247. print "(spurious?)";
  248. }
  249. print "\n";
  250. },
  251. #define SSH2_MSG_REQUEST_FAILURE 82 /* 0x52 */
  252. 'SSH2_MSG_REQUEST_FAILURE' => sub {
  253. my ($direction, $seq, $data) = @_;
  254. my $otherdir = ($direction eq "i" ? "o" : "i");
  255. my $request = shift @{$globalreq{$otherdir}};
  256. if (defined $request) {
  257. printf "to %s", $request->[0];
  258. } else {
  259. print "(spurious?)";
  260. }
  261. print "\n";
  262. },
  263. #define SSH2_MSG_CHANNEL_OPEN 90 /* 0x5a */
  264. 'SSH2_MSG_CHANNEL_OPEN' => sub {
  265. my ($direction, $seq, $data) = @_;
  266. my ($type, $sid, $winsize, $packet) = &parse("suuu", $data);
  267. # CHANNEL_OPEN tells the other side the _sender's_ id for the
  268. # channel, so this choice between "s" and "c" prefixes is
  269. # opposite to every other message in the protocol, which all
  270. # quote the _recipient's_ id of the channel.
  271. $sid = ($direction eq "i" ? "s" : "c") . $sid;
  272. my $chan = {'id'=>$sid, 'state'=>'halfopen',
  273. 'i'=>{'win'=>0, 'seq'=>0},
  274. 'o'=>{'win'=>0, 'seq'=>0}};
  275. $chan->{$direction}{'win'} = $winsize;
  276. push @channels, $chan;
  277. my $index = $#channels;
  278. $chan_by_id{$sid} = $index;
  279. printf "ch%d (%s) %s (--%d)", $index, $chan->{'id'}, $type,
  280. $chan->{$direction}{'win'};
  281. if ($type eq "x11") {
  282. my ($addr, $port) = &parse("su", $data);
  283. printf " from %s:%s", $addr, $port;
  284. } elsif ($type eq "forwarded-tcpip") {
  285. my ($saddr, $sport, $paddr, $pport) = &parse("susu", $data);
  286. printf " to %s:%s from %s:%s", $saddr, $sport, $paddr, $pport;
  287. } elsif ($type eq "direct-tcpip") {
  288. my ($daddr, $dport, $saddr, $sport) = &parse("susu", $data);
  289. printf " to %s:%s from %s:%s", $daddr, $dport, $saddr, $sport;
  290. }
  291. print "\n";
  292. },
  293. #define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 /* 0x5b */
  294. 'SSH2_MSG_CHANNEL_OPEN_CONFIRMATION' => sub {
  295. my ($direction, $seq, $data) = @_;
  296. my ($rid, $sid, $winsize, $packet) = &parse("uuuu", $data);
  297. $rid = ($direction eq "i" ? "c" : "s") . $rid;
  298. my $index = $chan_by_id{$rid};
  299. if (!defined $index) {
  300. printf "UNKNOWN_CHANNEL (%s) (--%d)\n", $rid, $winsize;
  301. return;
  302. }
  303. $sid = ($direction eq "i" ? "s" : "c") . $sid;
  304. $chan_by_id{$sid} = $index;
  305. my $chan = $channels[$index];
  306. $chan->{'id'} = ($direction eq "i" ? "$rid/$sid" : "$sid/$rid");
  307. $chan->{'state'} = 'open';
  308. $chan->{$direction}{'win'} = $winsize;
  309. printf "ch%d (%s) (--%d)\n", $index, $chan->{'id'},
  310. $chan->{$direction}{'win'};
  311. },
  312. #define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 /* 0x5c */
  313. 'SSH2_MSG_CHANNEL_OPEN_FAILURE' => sub {
  314. my ($direction, $seq, $data) = @_;
  315. my ($rid, $reason, $desc, $lang) = &parse("uuss", $data);
  316. $rid = ($direction eq "i" ? "c" : "s") . $rid;
  317. my $index = $chan_by_id{$rid};
  318. if (!defined $index) {
  319. printf "UNKNOWN_CHANNEL (%s) %s\n", $rid, &str($reason);
  320. return;
  321. }
  322. my $chan = $channels[$index];
  323. $chan->{'state'} = 'rejected';
  324. printf "ch%d (%s) %s\n", $index, $chan->{'id'}, &str($reason);
  325. },
  326. #define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 /* 0x5d */
  327. 'SSH2_MSG_CHANNEL_WINDOW_ADJUST' => sub {
  328. my ($direction, $seq, $data) = @_;
  329. my ($rid, $bytes) = &parse("uu", $data);
  330. $rid = ($direction eq "i" ? "c" : "s") . $rid;
  331. my $index = $chan_by_id{$rid};
  332. if (!defined $index) {
  333. printf "UNKNOWN_CHANNEL (%s) +%d\n", $rid, $bytes;
  334. return;
  335. }
  336. my $chan = $channels[$index];
  337. $chan->{$direction}{'win'} += $bytes;
  338. printf "ch%d (%s) +%d (--%d)\n", $index, $chan->{'id'}, $bytes,
  339. $chan->{$direction}{'win'};
  340. },
  341. #define SSH2_MSG_CHANNEL_DATA 94 /* 0x5e */
  342. 'SSH2_MSG_CHANNEL_DATA' => sub {
  343. my ($direction, $seq, $data) = @_;
  344. my ($rid, $bytes) = &parse("uu", $data);
  345. $rid = ($direction eq "i" ? "c" : "s") . $rid;
  346. my $index = $chan_by_id{$rid};
  347. if (!defined $index) {
  348. printf "UNKNOWN_CHANNEL (%s), %s bytes\n", $rid, $bytes;
  349. return;
  350. }
  351. my $chan = $channels[$index];
  352. $chan->{$direction}{'seq'} += $bytes;
  353. printf "ch%d (%s), %s bytes (%d--%d)\n", $index, $chan->{'id'}, $bytes,
  354. $chan->{$direction}{'seq'}-$bytes, $chan->{$direction}{'seq'};
  355. my @realdata = splice @$data, 0, $bytes;
  356. if ($dumpdata) {
  357. my $filekey = $direction . "file";
  358. if (!defined $chan->{$filekey}) {
  359. my $filename = sprintf "ch%d.%s", $index, $direction;
  360. $chan->{$filekey} = FileHandle->new(">$filename");
  361. if (!defined $chan->{$filekey}) {
  362. die "$filename: $!\n";
  363. }
  364. }
  365. die "channel data not present in $seq\n" if @realdata < $bytes;
  366. my $rawdata = pack "C*", @realdata;
  367. my $fh = $chan->{$filekey};
  368. print $fh $rawdata;
  369. }
  370. if (@realdata == $bytes and defined $chan->{$direction."data"}) {
  371. my $rawdata = pack "C*", @realdata;
  372. $chan->{$direction."data"}->($chan, $index, $direction, $rawdata);
  373. }
  374. },
  375. #define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 /* 0x5f */
  376. 'SSH2_MSG_CHANNEL_EXTENDED_DATA' => sub {
  377. my ($direction, $seq, $data) = @_;
  378. my ($rid, $type, $bytes) = &parse("uuu", $data);
  379. if ($type == 1) {
  380. $type = "SSH_EXTENDED_DATA_STDERR";
  381. }
  382. $rid = ($direction eq "i" ? "c" : "s") . $rid;
  383. my $index = $chan_by_id{$rid};
  384. if (!defined $index) {
  385. printf "UNKNOWN_CHANNEL (%s), type %s, %s bytes\n", $rid,
  386. $type, $bytes;
  387. return;
  388. }
  389. my $chan = $channels[$index];
  390. $chan->{$direction}{'seq'} += $bytes;
  391. printf "ch%d (%s), type %s, %s bytes (%d--%d)\n", $index,$chan->{'id'},
  392. $type, $bytes, $chan->{$direction}{'seq'}-$bytes,
  393. $chan->{$direction}{'seq'};
  394. my @realdata = splice @$data, 0, $bytes;
  395. if ($dumpdata) {
  396. # We treat EXTENDED_DATA as equivalent to DATA, for the
  397. # moment. It's not clear what else would be a better thing
  398. # to do with it, and this at least is the Right Answer if
  399. # the data is going to a terminal and the aim is to debug
  400. # the terminal emulator.
  401. my $filekey = $direction . "file";
  402. if (!defined $chan->{$filekey}) {
  403. my $filename = sprintf "ch%d.%s", $index, $direction;
  404. $chan->{$filekey} = FileHandle->new(">$filename");
  405. if (!defined $chan->{$filekey}) {
  406. die "$filename: $!\n";
  407. }
  408. }
  409. die "channel data not present in $seq\n" if @realdata < $bytes;
  410. my $rawdata = pack "C*", @realdata;
  411. my $fh = $chan->{$filekey};
  412. print $fh $rawdata;
  413. }
  414. if (@realdata == $bytes and defined $chan->{$direction."data"}) {
  415. my $rawdata = pack "C*", @realdata;
  416. $chan->{$direction."data"}->($chan, $index, $direction, $rawdata);
  417. }
  418. },
  419. #define SSH2_MSG_CHANNEL_EOF 96 /* 0x60 */
  420. 'SSH2_MSG_CHANNEL_EOF' => sub {
  421. my ($direction, $seq, $data) = @_;
  422. my ($rid) = &parse("uu", $data);
  423. $rid = ($direction eq "i" ? "c" : "s") . $rid;
  424. my $index = $chan_by_id{$rid};
  425. if (!defined $index) {
  426. printf "UNKNOWN_CHANNEL (%s)\n", $rid;
  427. return;
  428. }
  429. my $chan = $channels[$index];
  430. printf "ch%d (%s)\n", $index, $chan->{'id'};
  431. },
  432. #define SSH2_MSG_CHANNEL_CLOSE 97 /* 0x61 */
  433. 'SSH2_MSG_CHANNEL_CLOSE' => sub {
  434. my ($direction, $seq, $data) = @_;
  435. my ($rid) = &parse("uu", $data);
  436. $rid = ($direction eq "i" ? "c" : "s") . $rid;
  437. my $index = $chan_by_id{$rid};
  438. if (!defined $index) {
  439. printf "UNKNOWN_CHANNEL (%s)\n", $rid;
  440. return;
  441. }
  442. my $chan = $channels[$index];
  443. $chan->{'state'} = ($chan->{'state'} eq "open" ? "halfclosed" :
  444. $chan->{'state'} eq "halfclosed" ? "closed" :
  445. "confused");
  446. if ($chan->{'state'} eq "closed") {
  447. $chan->{'ifile'}->close if defined $chan->{'ifile'};
  448. $chan->{'ofile'}->close if defined $chan->{'ofile'};
  449. }
  450. printf "ch%d (%s)\n", $index, $chan->{'id'};
  451. },
  452. #define SSH2_MSG_CHANNEL_REQUEST 98 /* 0x62 */
  453. 'SSH2_MSG_CHANNEL_REQUEST' => sub {
  454. my ($direction, $seq, $data) = @_;
  455. my ($rid, $type, $wantreply) = &parse("usb", $data);
  456. $rid = ($direction eq "i" ? "c" : "s") . $rid;
  457. my $index = $chan_by_id{$rid};
  458. my $chan;
  459. if (!defined $index) {
  460. printf "UNKNOWN_CHANNEL (%s) %s (%s)", $rid,
  461. $type, $wantreply eq "yes" ? "reply" : "noreply";
  462. } else {
  463. $chan = $channels[$index];
  464. printf "ch%d (%s) %s (%s)", $index, $chan->{'id'},
  465. $type, $wantreply eq "yes" ? "reply" : "noreply";
  466. push @{$chan->{'requests_'.$direction}}, [$seq, $type]
  467. if $wantreply eq "yes";
  468. }
  469. if ($type eq "pty-req") {
  470. my ($term, $w, $h, $pw, $ph, $modes) = &parse("suuuus", $data);
  471. printf " %s %sx%s", &str($term), $w, $h;
  472. } elsif ($type eq "x11-req") {
  473. my ($single, $xprot, $xcookie, $xscreen) = &parse("bssu", $data);
  474. print " one-off" if $single eq "yes";
  475. printf " %s :%s", $xprot, $xscreen;
  476. } elsif ($type eq "exec") {
  477. my ($command) = &parse("s", $data);
  478. printf " %s", &str($command);
  479. } elsif ($type eq "subsystem") {
  480. my ($subsys) = &parse("s", $data);
  481. printf " %s", &str($subsys);
  482. if ($subsys eq "sftp") {
  483. &sftp_setup($index);
  484. }
  485. } elsif ($type eq "window-change") {
  486. my ($w, $h, $pw, $ph) = &parse("uuuu", $data);
  487. printf " %sx%s", $w, $h;
  488. } elsif ($type eq "xon-xoff") {
  489. my ($can) = &parse("b", $data);
  490. printf " %s", $can;
  491. } elsif ($type eq "signal") {
  492. my ($sig) = &parse("s", $data);
  493. printf " %s", &str($sig);
  494. } elsif ($type eq "exit-status") {
  495. my ($status) = &parse("u", $data);
  496. printf " %s", $status;
  497. } elsif ($type eq "exit-signal") {
  498. my ($sig, $core, $error, $lang) = &parse("sbss", $data);
  499. printf " %s", &str($sig);
  500. print " (core dumped)" if $core eq "yes";
  501. }
  502. print "\n";
  503. },
  504. #define SSH2_MSG_CHANNEL_SUCCESS 99 /* 0x63 */
  505. 'SSH2_MSG_CHANNEL_SUCCESS' => sub {
  506. my ($direction, $seq, $data) = @_;
  507. my ($rid) = &parse("uu", $data);
  508. $rid = ($direction eq "i" ? "c" : "s") . $rid;
  509. my $index = $chan_by_id{$rid};
  510. if (!defined $index) {
  511. printf "UNKNOWN_CHANNEL (%s)\n", $rid;
  512. return;
  513. }
  514. my $chan = $channels[$index];
  515. printf "ch%d (%s)", $index, $chan->{'id'};
  516. my $otherdir = ($direction eq "i" ? "o" : "i");
  517. my $request = shift @{$chan->{'requests_' . $otherdir}};
  518. if (defined $request) {
  519. printf " to %s", $request->[0];
  520. } else {
  521. print " (spurious?)";
  522. }
  523. print "\n";
  524. },
  525. #define SSH2_MSG_CHANNEL_FAILURE 100 /* 0x64 */
  526. 'SSH2_MSG_CHANNEL_FAILURE' => sub {
  527. my ($direction, $seq, $data) = @_;
  528. my ($rid) = &parse("uu", $data);
  529. $rid = ($direction eq "i" ? "c" : "s") . $rid;
  530. my $index = $chan_by_id{$rid};
  531. if (!defined $index) {
  532. printf "UNKNOWN_CHANNEL (%s)\n", $rid;
  533. return;
  534. }
  535. my $chan = $channels[$index];
  536. printf "ch%d (%s)", $index, $chan->{'id'};
  537. my $otherdir = ($direction eq "i" ? "o" : "i");
  538. my $request = shift @{$chan->{'requests_' . $otherdir}};
  539. if (defined $request) {
  540. printf " to %s", $request->[0];
  541. } else {
  542. print " (spurious?)";
  543. }
  544. print "\n";
  545. },
  546. #define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60
  547. 'SSH2_MSG_USERAUTH_GSSAPI_RESPONSE' => sub {
  548. my ($direction, $seq, $data) = @_;
  549. print "\n";
  550. },
  551. #define SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61
  552. 'SSH2_MSG_USERAUTH_GSSAPI_TOKEN' => sub {
  553. my ($direction, $seq, $data) = @_;
  554. print "\n";
  555. },
  556. #define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63
  557. 'SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE' => sub {
  558. my ($direction, $seq, $data) = @_;
  559. print "\n";
  560. },
  561. #define SSH2_MSG_USERAUTH_GSSAPI_ERROR 64
  562. 'SSH2_MSG_USERAUTH_GSSAPI_ERROR' => sub {
  563. my ($direction, $seq, $data) = @_;
  564. print "\n";
  565. },
  566. #define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65
  567. 'SSH2_MSG_USERAUTH_GSSAPI_ERRTOK' => sub {
  568. my ($direction, $seq, $data) = @_;
  569. print "\n";
  570. },
  571. #define SSH2_MSG_USERAUTH_GSSAPI_MIC 66
  572. 'SSH2_MSG_USERAUTH_GSSAPI_MIC' => sub {
  573. my ($direction, $seq, $data) = @_;
  574. print "\n";
  575. },
  576. );
  577. our %disc_reasons = (
  578. 1 => "SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT",
  579. 2 => "SSH_DISCONNECT_PROTOCOL_ERROR",
  580. 3 => "SSH_DISCONNECT_KEY_EXCHANGE_FAILED",
  581. 4 => "SSH_DISCONNECT_RESERVED",
  582. 5 => "SSH_DISCONNECT_MAC_ERROR",
  583. 6 => "SSH_DISCONNECT_COMPRESSION_ERROR",
  584. 7 => "SSH_DISCONNECT_SERVICE_NOT_AVAILABLE",
  585. 8 => "SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED",
  586. 9 => "SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE",
  587. 10 => "SSH_DISCONNECT_CONNECTION_LOST",
  588. 11 => "SSH_DISCONNECT_BY_APPLICATION",
  589. 12 => "SSH_DISCONNECT_TOO_MANY_CONNECTIONS",
  590. 13 => "SSH_DISCONNECT_AUTH_CANCELLED_BY_USER",
  591. 14 => "SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE",
  592. 15 => "SSH_DISCONNECT_ILLEGAL_USER_NAME",
  593. );
  594. my %verbose_packet_dump_functions = (
  595. 'SSH2_MSG_KEXINIT' => sub {
  596. my ($data) = @_;
  597. my ($cookie0, $cookie1, $cookie2, $cookie3,
  598. $kex, $hostkey, $cscipher, $sccipher, $csmac, $scmac,
  599. $cscompress, $sccompress, $cslang, $sclang, $guess, $reserved) =
  600. &parse("uuuussssssssssbu", $data);
  601. printf(" cookie: %08x%08x%08x%08x\n",
  602. $cookie0, $cookie1, $cookie2, $cookie3);
  603. my $print_namelist = sub {
  604. my @names = split /,/, $_[1];
  605. printf " %s: name-list with %d items%s\n", $_[0], (scalar @names),
  606. join "", map { "\n $_" } @names;
  607. };
  608. $print_namelist->("kex", $kex);
  609. $print_namelist->("host key", $hostkey);
  610. $print_namelist->("client->server cipher", $cscipher);
  611. $print_namelist->("server->client cipher", $sccipher);
  612. $print_namelist->("client->server MAC", $csmac);
  613. $print_namelist->("server->client MAC", $scmac);
  614. $print_namelist->("client->server compression", $cscompress);
  615. $print_namelist->("server->client compression", $sccompress);
  616. $print_namelist->("client->server language", $cslang);
  617. $print_namelist->("server->client language", $sclang);
  618. printf " first kex packet follows: %s\n", $guess;
  619. printf " reserved field: %#x\n", $reserved;
  620. },
  621. 'SSH2_MSG_KEXDH_INIT' => sub {
  622. my ($data) = @_;
  623. my ($e) = &parse("m", $data);
  624. printf " e: %s\n", $e;
  625. },
  626. 'SSH2_MSG_KEX_DH_GEX_REQUEST' => sub {
  627. my ($data) = @_;
  628. my ($min, $pref, $max) = &parse("uuu", $data);
  629. printf " min bits: %d\n", $min;
  630. printf " preferred bits: %d\n", $pref;
  631. printf " max bits: %d\n", $max;
  632. },
  633. 'SSH2_MSG_KEX_DH_GEX_GROUP' => sub {
  634. my ($data) = @_;
  635. my ($p, $g) = &parse("mm", $data);
  636. printf " p: %s\n", $p;
  637. printf " g: %s\n", $g;
  638. },
  639. 'SSH2_MSG_KEX_DH_GEX_INIT' => sub {
  640. my ($data) = @_;
  641. my ($e) = &parse("m", $data);
  642. printf " e: %s\n", $e;
  643. },
  644. 'SSH2_MSG_KEX_ECDH_INIT' => sub {
  645. my ($data) = @_;
  646. my ($cpv) = &parse("s", $data);
  647. # Public values in ECDH depend for their interpretation on the
  648. # selected curve, and this script doesn't cross-analyse the
  649. # two KEXINIT packets to independently figure out what that
  650. # curve is. So the best we can do is just dump the raw data.
  651. printf " client public value: %s\n", (unpack "H*", $cpv);
  652. },
  653. 'SSH2_MSG_KEXDH_REPLY' => sub {
  654. my ($data) = @_;
  655. my ($hostkeyblob, $f, $sigblob) = &parse("sms", $data);
  656. my ($hktype, @hostkey) = &parse_public_key($hostkeyblob);
  657. printf " host key: %s\n", $hktype;
  658. while (@hostkey) {
  659. my ($key, $value) = splice @hostkey, 0, 2;
  660. printf " $key: $value\n";
  661. }
  662. printf " f: %s\n", $f;
  663. printf " signature:\n";
  664. my @signature = &parse_signature($sigblob, $hktype);
  665. while (@signature) {
  666. my ($key, $value) = splice @signature, 0, 2;
  667. printf " $key: $value\n";
  668. }
  669. },
  670. 'SSH2_MSG_KEX_DH_GEX_REPLY' => sub {
  671. my ($data) = @_;
  672. my ($hostkeyblob, $f, $sigblob) = &parse("sms", $data);
  673. my ($hktype, @hostkey) = &parse_public_key($hostkeyblob);
  674. printf " host key: %s\n", $hktype;
  675. while (@hostkey) {
  676. my ($key, $value) = splice @hostkey, 0, 2;
  677. printf " $key: $value\n";
  678. }
  679. printf " f: %s\n", $f;
  680. printf " signature:\n";
  681. my @signature = &parse_signature($sigblob, $hktype);
  682. while (@signature) {
  683. my ($key, $value) = splice @signature, 0, 2;
  684. printf " $key: $value\n";
  685. }
  686. },
  687. 'SSH2_MSG_KEX_ECDH_REPLY' => sub {
  688. my ($data) = @_;
  689. my ($hostkeyblob, $spv, $sigblob) = &parse("sss", $data);
  690. my ($hktype, @hostkey) = &parse_public_key($hostkeyblob);
  691. printf " host key: %s\n", $hktype;
  692. while (@hostkey) {
  693. my ($key, $value) = splice @hostkey, 0, 2;
  694. printf " $key: $value\n";
  695. }
  696. printf " server public value: %s\n", (unpack "H*", $spv);
  697. printf " signature:\n";
  698. my @signature = &parse_signature($sigblob, $hktype);
  699. while (@signature) {
  700. my ($key, $value) = splice @signature, 0, 2;
  701. printf " $key: $value\n";
  702. }
  703. },
  704. 'SSH2_MSG_NEWKEYS' => sub {},
  705. 'SSH2_MSG_SERVICE_REQUEST' => sub {
  706. my ($data) = @_;
  707. my ($servname) = &parse("s", $data);
  708. printf " service name: %s\n", $servname;
  709. },
  710. 'SSH2_MSG_SERVICE_ACCEPT' => sub {
  711. my ($data) = @_;
  712. my ($servname) = &parse("s", $data);
  713. printf " service name: %s\n", $servname;
  714. },
  715. 'SSH2_MSG_DISCONNECT' => sub {
  716. my ($data) = @_;
  717. my ($reason, $desc, $lang) = &parse("uss", $data);
  718. printf(" reason code: %d%s\n", $reason,
  719. defined $disc_reasons{$reason} ?
  720. " ($disc_reasons{$reason})" : "" );
  721. printf " description: '%s'\n", $desc;
  722. printf " language tag: '%s'\n", $lang;
  723. },
  724. 'SSH2_MSG_DEBUG' => sub {
  725. my ($data) = @_;
  726. my ($display, $desc, $lang) = &parse("bss", $data);
  727. printf " always display: %s\n", $display;
  728. printf " description: '%s'\n", $desc;
  729. printf " language tag: '%s'\n", $lang;
  730. },
  731. 'SSH2_MSG_IGNORE' => sub {
  732. my ($data) = @_;
  733. my ($payload) = &parse("s", $data);
  734. printf " data: %s\n", unpack "H*", $payload;
  735. },
  736. 'SSH2_MSG_UNIMPLEMENTED' => sub {
  737. my ($data) = @_;
  738. my ($seq) = &parse("u", $data);
  739. printf " sequence number: %d\n", $seq;
  740. },
  741. 'SSH2_MSG_KEXGSS_INIT' => sub {
  742. my ($data) = @_;
  743. my ($token, $e) = &parse("sm", $data);
  744. printf " output token: %s\n", unpack "H*", $token;
  745. printf " e: %s\n", $e;
  746. },
  747. 'SSH2_MSG_KEXGSS_CONTINUE' => sub {
  748. my ($data) = @_;
  749. my ($token) = &parse("s", $data);
  750. printf " output token: %s\n", unpack "H*", $token;
  751. },
  752. 'SSH2_MSG_KEXGSS_COMPLETE' => sub {
  753. my ($data) = @_;
  754. my ($f, $permsgtoken, $got_output) = &parse("msb", $data);
  755. printf " f: %s\n", $f;
  756. printf " per-message token: %s\n", unpack "H*", $permsgtoken;
  757. printf " output token present: %s\n", $got_output;
  758. if ($got_output eq "yes") {
  759. my ($token) = &parse("s", $data);
  760. printf " output token: %s\n", unpack "H*", $token;
  761. }
  762. },
  763. 'SSH2_MSG_KEXGSS_HOSTKEY' => sub {
  764. my ($data) = @_;
  765. my ($hostkey) = &parse("s", $data);
  766. printf " host key: %s\n", unpack "H*", $hostkey;
  767. },
  768. 'SSH2_MSG_KEXGSS_ERROR' => sub {
  769. my ($data) = @_;
  770. my ($maj, $min, $msg, $lang) = &parse("uuss", $data);
  771. printf " major status: %d\n", $maj;
  772. printf " minor status: %d\n", $min;
  773. printf " message: '%s'\n", $msg;
  774. printf " language tag: '%s'\n", $lang;
  775. },
  776. 'SSH2_MSG_KEXGSS_GROUPREQ' => sub {
  777. my ($data) = @_;
  778. my ($min, $pref, $max) = &parse("uuu", $data);
  779. printf " min bits: %d\n", $min;
  780. printf " preferred bits: %d\n", $pref;
  781. printf " max bits: %d\n", $max;
  782. },
  783. 'SSH2_MSG_KEXGSS_GROUP' => sub {
  784. my ($data) = @_;
  785. my ($p, $g) = &parse("mm", $data);
  786. printf " p: %s\n", $p;
  787. printf " g: %s\n", $g;
  788. },
  789. );
  790. my %sftp_packets = (
  791. #define SSH_FXP_INIT 1 /* 0x1 */
  792. 0x1 => sub {
  793. my ($chan, $index, $direction, $id, $data) = @_;
  794. my ($ver) = &parse("u", $data);
  795. printf "SSH_FXP_INIT %d\n", $ver;
  796. },
  797. #define SSH_FXP_VERSION 2 /* 0x2 */
  798. 0x2 => sub {
  799. my ($chan, $index, $direction, $id, $data) = @_;
  800. my ($ver) = &parse("u", $data);
  801. printf "SSH_FXP_VERSION %d\n", $ver;
  802. },
  803. #define SSH_FXP_OPEN 3 /* 0x3 */
  804. 0x3 => sub {
  805. my ($chan, $index, $direction, $id, $data) = @_;
  806. my ($reqid, $path, $pflags) = &parse("usu", $data);
  807. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_OPEN");
  808. printf " \"%s\" ", $path;
  809. if ($pflags eq 0) {
  810. print "0";
  811. } else {
  812. my $sep = "";
  813. if ($pflags & 1) { $pflags ^= 1; print "${sep}READ"; $sep = "|"; }
  814. if ($pflags & 2) { $pflags ^= 2; print "${sep}WRITE"; $sep = "|"; }
  815. if ($pflags & 4) { $pflags ^= 4; print "${sep}APPEND"; $sep = "|"; }
  816. if ($pflags & 8) { $pflags ^= 8; print "${sep}CREAT"; $sep = "|"; }
  817. if ($pflags & 16) { $pflags ^= 16; print "${sep}TRUNC"; $sep = "|"; }
  818. if ($pflags & 32) { $pflags ^= 32; print "${sep}EXCL"; $sep = "|"; }
  819. if ($pflags) { print "${sep}${pflags}"; }
  820. }
  821. print "\n";
  822. },
  823. #define SSH_FXP_CLOSE 4 /* 0x4 */
  824. 0x4 => sub {
  825. my ($chan, $index, $direction, $id, $data) = @_;
  826. my ($reqid, $handle) = &parse("us", $data);
  827. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_CLOSE");
  828. printf " \"%s\"", &stringescape($handle);
  829. print "\n";
  830. },
  831. #define SSH_FXP_READ 5 /* 0x5 */
  832. 0x5 => sub {
  833. my ($chan, $index, $direction, $id, $data) = @_;
  834. my ($reqid, $handle, $offset, $len) = &parse("usUu", $data);
  835. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_READ");
  836. printf " \"%s\" %d %d", &stringescape($handle), $offset, $len;
  837. print "\n";
  838. },
  839. #define SSH_FXP_WRITE 6 /* 0x6 */
  840. 0x6 => sub {
  841. my ($chan, $index, $direction, $id, $data) = @_;
  842. my ($reqid, $handle, $offset, $wdata) = &parse("usUs", $data);
  843. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_WRITE");
  844. printf " \"%s\" %d [%d bytes]", &stringescape($handle), $offset, length $wdata;
  845. print "\n";
  846. },
  847. #define SSH_FXP_LSTAT 7 /* 0x7 */
  848. 0x7 => sub {
  849. my ($chan, $index, $direction, $id, $data) = @_;
  850. my ($reqid, $path) = &parse("us", $data);
  851. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_LSTAT");
  852. printf " \"%s\"", $path;
  853. print "\n";
  854. },
  855. #define SSH_FXP_FSTAT 8 /* 0x8 */
  856. 0x8 => sub {
  857. my ($chan, $index, $direction, $id, $data) = @_;
  858. my ($reqid, $handle) = &parse("us", $data);
  859. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_FSTAT");
  860. printf " \"%s\"", &stringescape($handle);
  861. print "\n";
  862. },
  863. #define SSH_FXP_SETSTAT 9 /* 0x9 */
  864. 0x9 => sub {
  865. my ($chan, $index, $direction, $id, $data) = @_;
  866. my ($reqid, $path) = &parse("us", $data);
  867. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_SETSTAT");
  868. my $attrs = &sftp_parse_attrs($data);
  869. printf " \"%s\" %s", $path, $attrs;
  870. print "\n";
  871. },
  872. #define SSH_FXP_FSETSTAT 10 /* 0xa */
  873. 0xa => sub {
  874. my ($chan, $index, $direction, $id, $data) = @_;
  875. my ($reqid, $handle) = &parse("us", $data);
  876. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_FSETSTAT");
  877. my $attrs = &sftp_parse_attrs($data);
  878. printf " \"%s\" %s", &stringescape($handle), $attrs;
  879. print "\n";
  880. },
  881. #define SSH_FXP_OPENDIR 11 /* 0xb */
  882. 0xb => sub {
  883. my ($chan, $index, $direction, $id, $data) = @_;
  884. my ($reqid, $path) = &parse("us", $data);
  885. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_OPENDIR");
  886. printf " \"%s\"", $path;
  887. print "\n";
  888. },
  889. #define SSH_FXP_READDIR 12 /* 0xc */
  890. 0xc => sub {
  891. my ($chan, $index, $direction, $id, $data) = @_;
  892. my ($reqid, $handle) = &parse("us", $data);
  893. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_READDIR");
  894. printf " \"%s\"", &stringescape($handle);
  895. print "\n";
  896. },
  897. #define SSH_FXP_REMOVE 13 /* 0xd */
  898. 0xd => sub {
  899. my ($chan, $index, $direction, $id, $data) = @_;
  900. my ($reqid, $path) = &parse("us", $data);
  901. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_REMOVE");
  902. printf " \"%s\"", $path;
  903. print "\n";
  904. },
  905. #define SSH_FXP_MKDIR 14 /* 0xe */
  906. 0xe => sub {
  907. my ($chan, $index, $direction, $id, $data) = @_;
  908. my ($reqid, $path) = &parse("us", $data);
  909. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_MKDIR");
  910. printf " \"%s\"", $path;
  911. print "\n";
  912. },
  913. #define SSH_FXP_RMDIR 15 /* 0xf */
  914. 0xf => sub {
  915. my ($chan, $index, $direction, $id, $data) = @_;
  916. my ($reqid, $path) = &parse("us", $data);
  917. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_RMDIR");
  918. printf " \"%s\"", $path;
  919. print "\n";
  920. },
  921. #define SSH_FXP_REALPATH 16 /* 0x10 */
  922. 0x10 => sub {
  923. my ($chan, $index, $direction, $id, $data) = @_;
  924. my ($reqid, $path) = &parse("us", $data);
  925. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_REALPATH");
  926. printf " \"%s\"", $path;
  927. print "\n";
  928. },
  929. #define SSH_FXP_STAT 17 /* 0x11 */
  930. 0x11 => sub {
  931. my ($chan, $index, $direction, $id, $data) = @_;
  932. my ($reqid, $path) = &parse("us", $data);
  933. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_STAT");
  934. printf " \"%s\"", $path;
  935. print "\n";
  936. },
  937. #define SSH_FXP_RENAME 18 /* 0x12 */
  938. 0x12 => sub {
  939. my ($chan, $index, $direction, $id, $data) = @_;
  940. my ($reqid, $srcpath, $dstpath) = &parse("uss", $data);
  941. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_RENAME");
  942. printf " \"%s\" \"%s\"", $srcpath, $dstpath;
  943. print "\n";
  944. },
  945. #define SSH_FXP_STATUS 101 /* 0x65 */
  946. 0x65 => sub {
  947. my ($chan, $index, $direction, $id, $data) = @_;
  948. my ($reqid, $status) = &parse("uu", $data);
  949. &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_STATUS");
  950. print " ";
  951. if ($status eq "0") { print "SSH_FX_OK"; }
  952. elsif ($status eq "1") { print "SSH_FX_EOF"; }
  953. elsif ($status eq "2") { print "SSH_FX_NO_SUCH_FILE"; }
  954. elsif ($status eq "3") { print "SSH_FX_PERMISSION_DENIED"; }
  955. elsif ($status eq "4") { print "SSH_FX_FAILURE"; }
  956. elsif ($status eq "5") { print "SSH_FX_BAD_MESSAGE"; }
  957. elsif ($status eq "6") { print "SSH_FX_NO_CONNECTION"; }
  958. elsif ($status eq "7") { print "SSH_FX_CONNECTION_LOST"; }
  959. elsif ($status eq "8") { print "SSH_FX_OP_UNSUPPORTED"; }
  960. else { printf "[unknown status %d]", $status; }
  961. print "\n";
  962. },
  963. #define SSH_FXP_HANDLE 102 /* 0x66 */
  964. 0x66 => sub {
  965. my ($chan, $index, $direction, $id, $data) = @_;
  966. my ($reqid, $handle) = &parse("us", $data);
  967. &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_HANDLE");
  968. printf " \"%s\"", &stringescape($handle);
  969. print "\n";
  970. },
  971. #define SSH_FXP_DATA 103 /* 0x67 */
  972. 0x67 => sub {
  973. my ($chan, $index, $direction, $id, $data) = @_;
  974. my ($reqid, $retdata) = &parse("us", $data);
  975. &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_DATA");
  976. printf " [%d bytes]", length $retdata;
  977. print "\n";
  978. },
  979. #define SSH_FXP_NAME 104 /* 0x68 */
  980. 0x68 => sub {
  981. my ($chan, $index, $direction, $id, $data) = @_;
  982. my ($reqid, $count) = &parse("uu", $data);
  983. &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_NAME");
  984. for my $i (1..$count) {
  985. my ($name, $longname) = &parse("ss", $data);
  986. my $attrs = &sftp_parse_attrs($data);
  987. print " [name=\"$name\", longname=\"$longname\", attrs=$attrs]";
  988. }
  989. print "\n";
  990. },
  991. #define SSH_FXP_ATTRS 105 /* 0x69 */
  992. 0x69 => sub {
  993. my ($chan, $index, $direction, $id, $data) = @_;
  994. my ($reqid) = &parse("u", $data);
  995. &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_ATTRS");
  996. my $attrs = &sftp_parse_attrs($data);
  997. printf " %s", $attrs;
  998. print "\n";
  999. },
  1000. #define SSH_FXP_EXTENDED 200 /* 0xc8 */
  1001. 0xc8 => sub {
  1002. my ($chan, $index, $direction, $id, $data) = @_;
  1003. my ($reqid, $type) = &parse("us", $data);
  1004. &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_EXTENDED");
  1005. printf " \"%s\"", $type;
  1006. print "\n";
  1007. },
  1008. #define SSH_FXP_EXTENDED_REPLY 201 /* 0xc9 */
  1009. 0xc9 => sub {
  1010. my ($chan, $index, $direction, $id, $data) = @_;
  1011. my ($reqid) = &parse("u", $data);
  1012. print "\n";
  1013. &sftp_logreply($chan, $direction, $reqid,$id,"SSH_FXP_EXTENDED_REPLY");
  1014. },
  1015. );
  1016. for my $type (keys %verbose_packet) {
  1017. if (!defined $verbose_packet_dump_functions{$type}) {
  1018. die "no verbose dump available for packet type $type\n";
  1019. }
  1020. }
  1021. my ($direction, $seq, $ourseq, $type, $data, $recording);
  1022. my %ourseqs = ('i'=>0, 'o'=>0);
  1023. $recording = 0;
  1024. while (<<>>) {
  1025. if ($recording) {
  1026. if (/^ [0-9a-fA-F]{8} ((?:[0-9a-fA-F]{2} )*[0-9a-fA-F]{2})/) {
  1027. push @$data, map { $_ eq "XX" ? -1 : hex $_ } split / /, $1;
  1028. } else {
  1029. $recording = 0;
  1030. my $fullseq = "$direction$ourseq";
  1031. print "$fullseq: $type ";
  1032. my ($verbose_dump, $verbose_data) = undef;
  1033. if (defined $verbose_packet_dump_functions{$type} &&
  1034. ($verbose_all || defined $verbose_packet{$type})) {
  1035. $verbose_dump = $verbose_packet_dump_functions{$type};
  1036. $verbose_data = [ @$data ];
  1037. }
  1038. if (defined $packets{$type}) {
  1039. $packets{$type}->($direction, $fullseq, $data);
  1040. } else {
  1041. printf "raw %s\n", join "", map { sprintf "%02x", $_ } @$data;
  1042. }
  1043. if (defined $verbose_dump) {
  1044. $verbose_dump->($verbose_data);
  1045. if (@$verbose_data) {
  1046. printf(" trailing bytes: %s\n",
  1047. unpack "H*", pack "C*", @$verbose_data);
  1048. }
  1049. }
  1050. }
  1051. }
  1052. if (/^(Incoming|Outgoing) packet #0x([0-9a-fA-F]+), type \d+ \/ 0x[0-9a-fA-F]+ \((.*)\)/) {
  1053. $direction = ($1 eq "Incoming" ? 'i' : 'o');
  1054. # $seq is the sequence number quoted in the log file. $ourseq
  1055. # is our own count of the sequence number, which differs in
  1056. # that it shouldn't wrap at 2^32, should anyone manage to run
  1057. # this script over such a huge log file.
  1058. $seq = hex $2;
  1059. $ourseq = $ourseqs{$direction}++;
  1060. $type = $3;
  1061. $data = [];
  1062. $recording = 1;
  1063. }
  1064. if ($pass_through_events && m/^Event Log: ([^\n]*)$/) {
  1065. printf "event: $1\n";
  1066. }
  1067. }
  1068. if ($dumpchannels) {
  1069. my %stateorder = ('closed'=>0, 'rejected'=>1,
  1070. 'halfclosed'=>2, 'open'=>3, 'halfopen'=>4);
  1071. for my $index (0..$#channels) {
  1072. my $chan = $channels[$index];
  1073. my $so = $stateorder{$chan->{'state'}};
  1074. $so = 1000 unless defined $so; # any state I've missed above comes last
  1075. $chan->{'index'} = sprintf "ch%d", $index;
  1076. $chan->{'order'} = sprintf "%08d %08d", $so, $index;
  1077. }
  1078. my @sortedchannels = sort { $a->{'order'} cmp $b->{'order'} } @channels;
  1079. for my $chan (@sortedchannels) {
  1080. printf "%s (%s): %s\n", $chan->{'index'}, $chan->{'id'}, $chan->{'state'};
  1081. }
  1082. }
  1083. sub format_unsigned_hex_integer {
  1084. my $abs = join "", map { sprintf "%02x", $_ } @_;
  1085. $abs =~ s!^0*!!g;
  1086. $abs = "0" if $abs eq "";
  1087. return "0x" . $abs;
  1088. }
  1089. sub parseone {
  1090. my ($type, $data) = @_;
  1091. if ($type eq "u") { # uint32
  1092. my @bytes = splice @$data, 0, 4;
  1093. return "<missing>" if @bytes < 4 or grep { $_<0 } @bytes;
  1094. return unpack "N", pack "C*", @bytes;
  1095. } elsif ($type eq "U") { # uint64
  1096. my @bytes = splice @$data, 0, 8;
  1097. return "<missing>" if @bytes < 8 or grep { $_<0 } @bytes;
  1098. my @words = unpack "NN", pack "C*", @bytes;
  1099. return ($words[0] << 32) + $words[1];
  1100. } elsif ($type eq "b") { # boolean
  1101. my $byte = shift @$data;
  1102. return "<missing>" if !defined $byte or $byte < 0;
  1103. return $byte ? "yes" : "no";
  1104. } elsif ($type eq "B") { # byte
  1105. my $byte = shift @$data;
  1106. return "<missing>" if !defined $byte or $byte < 0;
  1107. return $byte;
  1108. } elsif ($type eq "s" or $type eq "m") { # string, mpint
  1109. my @bytes = splice @$data, 0, 4;
  1110. return "<missing>" if @bytes < 4 or grep { $_<0 } @bytes;
  1111. my $len = unpack "N", pack "C*", @bytes;
  1112. @bytes = splice @$data, 0, $len;
  1113. return "<missing>" if @bytes < $len or grep { $_<0 } @bytes;
  1114. if ($type eq "m") {
  1115. my $str = "";
  1116. if ($bytes[0] >= 128) {
  1117. # Take two's complement.
  1118. @bytes = map { 0xFF ^ $_ } @bytes;
  1119. for my $i (reverse 0..$#bytes) {
  1120. if ($bytes[$i] < 0xFF) {
  1121. $bytes[$i]++;
  1122. last;
  1123. } else {
  1124. $bytes[$i] = 0;
  1125. }
  1126. }
  1127. $str = "-";
  1128. }
  1129. $str .= &format_unsigned_hex_integer(@bytes);
  1130. return $str;
  1131. } else {
  1132. return pack "C*", @bytes;
  1133. }
  1134. }
  1135. }
  1136. sub parse {
  1137. my ($template, $data) = @_;
  1138. return map { &parseone($_, $data) } split //, $template;
  1139. }
  1140. sub str {
  1141. # Quote as a string. If I get enthusiastic I might arrange for
  1142. # strange characters inside the string to be quoted.
  1143. my $str = shift @_;
  1144. return "'$str'";
  1145. }
  1146. sub sftp_setup {
  1147. my $index = shift @_;
  1148. my $chan = $channels[$index];
  1149. $chan->{'obuf'} = $chan->{'ibuf'} = '';
  1150. $chan->{'ocnt'} = $chan->{'icnt'} = 0;
  1151. $chan->{'odata'} = $chan->{'idata'} = \&sftp_data;
  1152. $chan->{'sftpreqs'} = {};
  1153. }
  1154. sub sftp_data {
  1155. my ($chan, $index, $direction, $data) = @_;
  1156. my $buf = \$chan->{$direction."buf"};
  1157. my $cnt = \$chan->{$direction."cnt"};
  1158. $$buf .= $data;
  1159. while (length $$buf >= 4) {
  1160. my $msglen = unpack "N", $$buf;
  1161. last if length $$buf < 4 + $msglen;
  1162. my $msg = substr $$buf, 4, $msglen;
  1163. $$buf = substr $$buf, 4 + $msglen;
  1164. $msg = [unpack "C*", $msg];
  1165. my $type = shift @$msg;
  1166. my $id = sprintf "ch%d_sftp_%s%d", $index, $direction, ${$cnt}++;
  1167. print "$id: ";
  1168. if (defined $sftp_packets{$type}) {
  1169. $sftp_packets{$type}->($chan, $index, $direction, $id, $msg);
  1170. } else {
  1171. printf "unknown SFTP packet type %d\n", $type;
  1172. }
  1173. }
  1174. }
  1175. sub sftp_logreq {
  1176. my ($chan, $direction, $reqid, $id, $name) = @_;
  1177. print "$name";
  1178. if ($direction eq "o") { # requests coming _in_ are too weird to track
  1179. $chan->{'sftpreqs'}->{$reqid} = $id;
  1180. }
  1181. }
  1182. sub sftp_logreply {
  1183. my ($chan, $direction, $reqid, $id, $name) = @_;
  1184. print "$name";
  1185. if ($direction eq "i") { # replies going _out_ are too weird to track
  1186. if (defined $chan->{'sftpreqs'}->{$reqid}) {
  1187. print " to ", $chan->{'sftpreqs'}->{$reqid};
  1188. $chan->{'sftpreqs'}->{$reqid} = undef;
  1189. }
  1190. }
  1191. }
  1192. sub sftp_parse_attrs {
  1193. my ($data) = @_;
  1194. my ($flags) = &parse("u", $data);
  1195. return $flags if $flags eq "<missing>";
  1196. my $out = "{";
  1197. my $sep = "";
  1198. if ($flags & 0x00000001) { # SSH_FILEXFER_ATTR_SIZE
  1199. $out .= $sep . sprintf "size=%d", &parse("U", $data);
  1200. $sep = ", ";
  1201. }
  1202. if ($flags & 0x00000002) { # SSH_FILEXFER_ATTR_UIDGID
  1203. $out .= $sep . sprintf "uid=%d", &parse("u", $data);
  1204. $out .= $sep . sprintf "gid=%d", &parse("u", $data);
  1205. $sep = ", ";
  1206. }
  1207. if ($flags & 0x00000004) { # SSH_FILEXFER_ATTR_PERMISSIONS
  1208. $out .= $sep . sprintf "perms=%#o", &parse("u", $data);
  1209. $sep = ", ";
  1210. }
  1211. if ($flags & 0x00000008) { # SSH_FILEXFER_ATTR_ACMODTIME
  1212. $out .= $sep . sprintf "atime=%d", &parse("u", $data);
  1213. $out .= $sep . sprintf "mtime=%d", &parse("u", $data);
  1214. $sep = ", ";
  1215. }
  1216. if ($flags & 0x80000000) { # SSH_FILEXFER_ATTR_EXTENDED
  1217. my $extcount = &parse("u", $data);
  1218. while ($extcount-- > 0) {
  1219. $out .= $sep . sprintf "\"%s\"=\"%s\"", &parse("ss", $data);
  1220. $sep = ", ";
  1221. }
  1222. }
  1223. $out .= "}";
  1224. return $out;
  1225. }
  1226. sub parse_public_key {
  1227. my ($blob) = @_;
  1228. my $data = [ unpack "C*", $blob ];
  1229. my @toret;
  1230. my ($type) = &parse("s", $data);
  1231. push @toret, $type;
  1232. if ($type eq "ssh-rsa") {
  1233. my ($e, $n) = &parse("mm", $data);
  1234. push @toret, "e", $e, "n", $n;
  1235. } elsif ($type eq "ssh-dss") {
  1236. my ($p, $q, $g, $y) = &parse("mmmm", $data);
  1237. push @toret, "p", $p, "q", $q, "g", $g, "y", $y;
  1238. } elsif ($type eq "ssh-ed25519") {
  1239. my ($xyblob) = &parse("s", $data);
  1240. my @y = unpack "C*", $xyblob;
  1241. push @toret, "hibit(x)", $y[$#y] & 1;
  1242. $y[$#y] &= ~1;
  1243. push @toret, "y & ~1", &format_unsigned_hex_integer(@y);
  1244. } elsif ($type =~ m!^ecdsa-sha2-nistp(256|384|521)$!) {
  1245. my ($curvename, $blob) = &parse("ss", $data);
  1246. push @toret, "curve name", $curvename;
  1247. my @blobdata = unpack "C*", $blob;
  1248. my ($fmt) = &parse("B", \@blobdata);
  1249. push @toret, "format byte", $fmt;
  1250. if ($fmt == 4) {
  1251. push @toret, "x", &format_unsigned_hex_integer(
  1252. @blobdata[0..($#blobdata+1)/2-1]);
  1253. push @toret, "y", &format_unsigned_hex_integer(
  1254. @blobdata[($#blobdata+1)/2..$#blobdata]);
  1255. }
  1256. } else {
  1257. push @toret, "undecoded data", unpack "H*", pack "C*", @$data;
  1258. }
  1259. return @toret;
  1260. };
  1261. sub parse_signature {
  1262. my ($blob, $keytype) = @_;
  1263. my $data = [ unpack "C*", $blob ];
  1264. my @toret;
  1265. if ($keytype eq "ssh-rsa") {
  1266. my ($type, $s) = &parse("ss", $data);
  1267. push @toret, "sig type", $type;
  1268. push @toret, "s", &format_unsigned_hex_integer(unpack "C*", $s);
  1269. } elsif ($keytype eq "ssh-dss") {
  1270. my ($type, $subblob) = &parse("ss", $data);
  1271. push @toret, "sig type", $type;
  1272. push @toret, "r", &format_unsigned_hex_integer(
  1273. unpack "C*", substr($subblob, 0, 20));
  1274. push @toret, "s", &format_unsigned_hex_integer(
  1275. unpack "C*", substr($subblob, 20, 40));
  1276. } elsif ($keytype eq "ssh-ed25519") {
  1277. my ($type, $rsblob) = &parse("ss", $data);
  1278. push @toret, "sig type", $type;
  1279. my @ry = unpack "C*", $rsblob;
  1280. my @sy = splice @ry, 32, 32;
  1281. push @toret, "hibit(r.x)", $ry[$#ry] & 1;
  1282. $ry[$#ry] &= ~1;
  1283. push @toret, "r.y & ~1", &format_unsigned_hex_integer(@ry);
  1284. push @toret, "hibit(s.x)", $sy[$#sy] & 1;
  1285. $sy[$#sy] &= ~1;
  1286. push @toret, "s.y & ~1", &format_unsigned_hex_integer(@sy);
  1287. } elsif ($keytype =~ m!^ecdsa-sha2-nistp(256|384|521)$!) {
  1288. my ($sigtype, $subblob) = &parse("ss", $data);
  1289. push @toret, "sig type", $sigtype;
  1290. my @sbdata = unpack "C*", $subblob;
  1291. my ($r, $s) = &parse("mm", \@sbdata);
  1292. push @toret, "r", $r, "s", $s;
  1293. } else {
  1294. push @toret, "undecoded data", unpack "H*", pack "C*", @$data;
  1295. }
  1296. return @toret;
  1297. };
  1298. sub stringescape {
  1299. my ($str) = @_;
  1300. $str =~ s!\\!\\\\!g;
  1301. $str =~ s![^ -~]!sprintf "\\x%02X", ord $&!eg;
  1302. return $str;
  1303. }