]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - contrib/logparse.pl
Update version number for 0.66 release.
[PuTTY.git] / contrib / logparse.pl
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use FileHandle;
6
7 my $dumpchannels = 0;
8 my $dumpdata = 0;
9 while ($ARGV[0] =~ /^-/) {
10     my $opt = shift @ARGV;
11     if ($opt eq "--") {
12         last; # stop processing options
13     } elsif ($opt eq "-c") {
14         $dumpchannels = 1;
15     } elsif ($opt eq "-d") {
16         $dumpdata = 1;
17     } else {
18         die "unrecognised option '$opt'\n";
19     }
20 }
21
22 my @channels = (); # ultimate channel ids are indices in this array
23 my %chan_by_id = (); # indexed by 'c%d' or 's%d' for client and server ids
24 my %globalreq = (); # indexed by 'i' or 'o'
25
26 my %packets = (
27 #define SSH2_MSG_DISCONNECT                       1     /* 0x1 */
28     'SSH2_MSG_DISCONNECT' => sub {
29         my ($direction, $seq, $data) = @_;
30         my ($reason, $description, $lang) = &parse("uss", $data);
31         printf "%s\n", &str($description);
32     },
33 #define SSH2_MSG_IGNORE                           2     /* 0x2 */
34     'SSH2_MSG_IGNORE' => sub {
35         my ($direction, $seq, $data) = @_;
36         my ($str) = &parse("s", $data);
37         printf "(%d bytes)\n", length $str;
38     },
39 #define SSH2_MSG_UNIMPLEMENTED                    3     /* 0x3 */
40     'SSH2_MSG_UNIMPLEMENTED' => sub {
41         my ($direction, $seq, $data) = @_;
42         my ($rseq) = &parse("u", $data);
43         printf "i%d\n", $rseq;
44     },
45 #define SSH2_MSG_DEBUG                            4     /* 0x4 */
46     'SSH2_MSG_DEBUG' => sub {
47         my ($direction, $seq, $data) = @_;
48         my ($disp, $message, $lang) = &parse("bss", $data);
49         printf "%s\n", &str($message);
50     },
51 #define SSH2_MSG_SERVICE_REQUEST                  5     /* 0x5 */
52     'SSH2_MSG_SERVICE_REQUEST' => sub {
53         my ($direction, $seq, $data) = @_;
54         my ($service) = &parse("s", $data);
55         printf "%s\n", &str($service);
56     },
57 #define SSH2_MSG_SERVICE_ACCEPT                   6     /* 0x6 */
58     'SSH2_MSG_SERVICE_ACCEPT' => sub {
59         my ($direction, $seq, $data) = @_;
60         my ($service) = &parse("s", $data);
61         printf "%s\n", &str($service);
62     },
63 #define SSH2_MSG_KEXINIT                          20    /* 0x14 */
64     'SSH2_MSG_KEXINIT' => sub {
65         my ($direction, $seq, $data) = @_;
66         print "\n";
67     },
68 #define SSH2_MSG_NEWKEYS                          21    /* 0x15 */
69     'SSH2_MSG_NEWKEYS' => sub {
70         my ($direction, $seq, $data) = @_;
71         print "\n";
72     },
73 #define SSH2_MSG_KEXDH_INIT                       30    /* 0x1e */
74     'SSH2_MSG_KEXDH_INIT' => sub {
75         my ($direction, $seq, $data) = @_;
76         print "\n";
77     },
78 #define SSH2_MSG_KEXDH_REPLY                      31    /* 0x1f */
79     'SSH2_MSG_KEXDH_REPLY' => sub {
80         my ($direction, $seq, $data) = @_;
81         print "\n";
82     },
83 #define SSH2_MSG_KEX_DH_GEX_REQUEST               30    /* 0x1e */
84     'SSH2_MSG_KEX_DH_GEX_REQUEST' => sub {
85         my ($direction, $seq, $data) = @_;
86         print "\n";
87     },
88 #define SSH2_MSG_KEX_DH_GEX_GROUP                 31    /* 0x1f */
89     'SSH2_MSG_KEX_DH_GEX_GROUP' => sub {
90         my ($direction, $seq, $data) = @_;
91         print "\n";
92     },
93 #define SSH2_MSG_KEX_DH_GEX_INIT                  32    /* 0x20 */
94     'SSH2_MSG_KEX_DH_GEX_INIT' => sub {
95         my ($direction, $seq, $data) = @_;
96         print "\n";
97     },
98 #define SSH2_MSG_KEX_DH_GEX_REPLY                 33    /* 0x21 */
99     'SSH2_MSG_KEX_DH_GEX_REPLY' => sub {
100         my ($direction, $seq, $data) = @_;
101         print "\n";
102     },
103 #define SSH2_MSG_KEXRSA_PUBKEY                    30    /* 0x1e */
104     'SSH2_MSG_KEXRSA_PUBKEY' => sub {
105         my ($direction, $seq, $data) = @_;
106         print "\n";
107     },
108 #define SSH2_MSG_KEXRSA_SECRET                    31    /* 0x1f */
109     'SSH2_MSG_KEXRSA_SECRET' => sub {
110         my ($direction, $seq, $data) = @_;
111         print "\n";
112     },
113 #define SSH2_MSG_KEXRSA_DONE                      32    /* 0x20 */
114     'SSH2_MSG_KEXRSA_DONE' => sub {
115         my ($direction, $seq, $data) = @_;
116         print "\n";
117     },
118 #define SSH2_MSG_USERAUTH_REQUEST                 50    /* 0x32 */
119     'SSH2_MSG_USERAUTH_REQUEST' => sub {
120         my ($direction, $seq, $data) = @_;
121         my ($user, $service, $method) = &parse("sss", $data);
122         my $out = sprintf "%s %s %s",
123             &str($user), &str($service), &str($method);
124         if ($method eq "publickey") {
125             my ($real) = &parse("b", $data);
126             $out .= " real=$real";
127         } elsif ($method eq "password") {
128             my ($change) = &parse("b", $data);
129             $out .= " change=$change";
130         }
131         print "$out\n";
132     },
133 #define SSH2_MSG_USERAUTH_FAILURE                 51    /* 0x33 */
134     'SSH2_MSG_USERAUTH_FAILURE' => sub {
135         my ($direction, $seq, $data) = @_;
136         my ($options) = &parse("s", $data);
137         printf "%s\n", &str($options);
138     },
139 #define SSH2_MSG_USERAUTH_SUCCESS                 52    /* 0x34 */
140     'SSH2_MSG_USERAUTH_SUCCESS' => sub {
141         my ($direction, $seq, $data) = @_;
142         print "\n";
143     },
144 #define SSH2_MSG_USERAUTH_BANNER                  53    /* 0x35 */
145     'SSH2_MSG_USERAUTH_BANNER' => sub {
146         my ($direction, $seq, $data) = @_;
147         print "\n";
148     },
149 #define SSH2_MSG_USERAUTH_PK_OK                   60    /* 0x3c */
150     'SSH2_MSG_USERAUTH_PK_OK' => sub {
151         my ($direction, $seq, $data) = @_;
152         print "\n";
153     },
154 #define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ        60    /* 0x3c */
155     'SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ' => sub {
156         my ($direction, $seq, $data) = @_;
157         print "\n";
158     },
159 #define SSH2_MSG_USERAUTH_INFO_REQUEST            60    /* 0x3c */
160     'SSH2_MSG_USERAUTH_INFO_REQUEST' => sub {
161         my ($direction, $seq, $data) = @_;
162         print "\n";
163     },
164 #define SSH2_MSG_USERAUTH_INFO_RESPONSE           61    /* 0x3d */
165     'SSH2_MSG_USERAUTH_INFO_RESPONSE' => sub {
166         my ($direction, $seq, $data) = @_;
167         print "\n";
168     },
169 #define SSH2_MSG_GLOBAL_REQUEST                   80    /* 0x50 */
170     'SSH2_MSG_GLOBAL_REQUEST' => sub {
171         my ($direction, $seq, $data) = @_;
172         my ($type, $wantreply) = &parse("sb", $data);
173         printf "%s (%s)", $type, $wantreply eq "yes" ? "reply" : "noreply";
174         my $request = [$seq, $type];
175         push @{$globalreq{$direction}}, $request if $wantreply eq "yes";
176         if ($type eq "tcpip-forward" or $type eq "cancel-tcpip-forward") {
177             my ($addr, $port) = &parse("su", $data);
178             printf " %s:%s", $addr, $port;
179             push @$request, $port;
180         }
181         print "\n";
182     },
183 #define SSH2_MSG_REQUEST_SUCCESS                  81    /* 0x51 */
184     'SSH2_MSG_REQUEST_SUCCESS' => sub {
185         my ($direction, $seq, $data) = @_;
186         my $otherdir = ($direction eq "i" ? "o" : "i");
187         my $request = shift @{$globalreq{$otherdir}};
188         if (defined $request) {
189             printf "to %s", $request->[0];
190             if ($request->[1] eq "tcpip-forward" and $request->[2] == 0) {
191                 my ($port) = &parse("u", $data);
192                 printf " port=%s", $port;
193             }
194         } else {
195             print "(spurious?)";
196         }
197         print "\n";
198     },
199 #define SSH2_MSG_REQUEST_FAILURE                  82    /* 0x52 */
200     'SSH2_MSG_REQUEST_FAILURE' => sub {
201         my ($direction, $seq, $data) = @_;
202         my $otherdir = ($direction eq "i" ? "o" : "i");
203         my $request = shift @{$globalreq{$otherdir}};
204         if (defined $request) {
205             printf "to %s", $request->[0];
206         } else {
207             print "(spurious?)";
208         }
209         print "\n";
210     },
211 #define SSH2_MSG_CHANNEL_OPEN                     90    /* 0x5a */
212     'SSH2_MSG_CHANNEL_OPEN' => sub {
213         my ($direction, $seq, $data) = @_;
214         my ($type, $sid, $winsize, $packet) = &parse("suuu", $data);
215         # CHANNEL_OPEN tells the other side the _sender's_ id for the
216         # channel, so this choice between "s" and "c" prefixes is
217         # opposite to every other message in the protocol, which all
218         # quote the _recipient's_ id of the channel.
219         $sid = ($direction eq "i" ? "s" : "c") . $sid;
220         my $chan = {'id'=>$sid, 'state'=>'halfopen',
221                     'i'=>{'win'=>0, 'seq'=>0},
222                     'o'=>{'win'=>0, 'seq'=>0}};
223         $chan->{$direction}{'win'} = $winsize;
224         push @channels, $chan;
225         my $index = $#channels;
226         $chan_by_id{$sid} = $index;
227         printf "ch%d (%s) %s (--%d)", $index, $chan->{'id'}, $type,
228             $chan->{$direction}{'win'};
229         if ($type eq "x11") {
230             my ($addr, $port) = &parse("su", $data);
231             printf " from %s:%s", $addr, $port;
232         } elsif ($type eq "forwarded-tcpip") {
233             my ($saddr, $sport, $paddr, $pport) = &parse("susu", $data);
234             printf " to %s:%s from %s:%s", $saddr, $sport, $paddr, $pport;
235         } elsif ($type eq "direct-tcpip") {
236             my ($daddr, $dport, $saddr, $sport) = &parse("susu", $data);
237             printf " to %s:%s from %s:%s", $daddr, $dport, $saddr, $sport;
238         }
239         print "\n";
240     },
241 #define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION        91    /* 0x5b */
242     'SSH2_MSG_CHANNEL_OPEN_CONFIRMATION' => sub {
243         my ($direction, $seq, $data) = @_;
244         my ($rid, $sid, $winsize, $packet) = &parse("uuuu", $data);
245         $rid = ($direction eq "i" ? "c" : "s") . $rid;
246         my $index = $chan_by_id{$rid};
247         if (!defined $index) {
248             printf "UNKNOWN_CHANNEL (%s) (--%d)\n", $rid, $winsize;
249             return;
250         }
251         $sid = ($direction eq "i" ? "s" : "c") . $sid;
252         $chan_by_id{$sid} = $index;
253         my $chan = $channels[$index];
254         $chan->{'id'} = ($direction eq "i" ? "$rid/$sid" : "$sid/$rid");
255         $chan->{'state'} = 'open';
256         $chan->{$direction}{'win'} = $winsize;
257         printf "ch%d (%s) (--%d)\n", $index, $chan->{'id'},
258             $chan->{$direction}{'win'};
259     },
260 #define SSH2_MSG_CHANNEL_OPEN_FAILURE             92    /* 0x5c */
261     'SSH2_MSG_CHANNEL_OPEN_FAILURE' => sub {
262         my ($direction, $seq, $data) = @_;
263         my ($rid, $reason, $desc, $lang) = &parse("uuss", $data);
264         $rid = ($direction eq "i" ? "c" : "s") . $rid;
265         my $index = $chan_by_id{$rid};
266         if (!defined $index) {
267             printf "UNKNOWN_CHANNEL (%s) %s\n", $rid, &str($reason);
268             return;
269         }
270         my $chan = $channels[$index];
271         $chan->{'state'} = 'rejected';
272         printf "ch%d (%s) %s\n", $index, $chan->{'id'}, &str($reason);
273     },
274 #define SSH2_MSG_CHANNEL_WINDOW_ADJUST            93    /* 0x5d */
275     'SSH2_MSG_CHANNEL_WINDOW_ADJUST' => sub {
276         my ($direction, $seq, $data) = @_;
277         my ($rid, $bytes) = &parse("uu", $data);
278         $rid = ($direction eq "i" ? "c" : "s") . $rid;
279         my $index = $chan_by_id{$rid};
280         if (!defined $index) {
281             printf "UNKNOWN_CHANNEL (%s) +%d\n", $rid, $bytes;
282             return;
283         }
284         my $chan = $channels[$index];
285         $chan->{$direction}{'win'} += $bytes;
286         printf "ch%d (%s) +%d (--%d)\n", $index, $chan->{'id'}, $bytes,
287             $chan->{$direction}{'win'};
288     },
289 #define SSH2_MSG_CHANNEL_DATA                     94    /* 0x5e */
290     'SSH2_MSG_CHANNEL_DATA' => sub {
291         my ($direction, $seq, $data) = @_;
292         my ($rid, $bytes) = &parse("uu", $data);
293         $rid = ($direction eq "i" ? "c" : "s") . $rid;
294         my $index = $chan_by_id{$rid};
295         if (!defined $index) {
296             printf "UNKNOWN_CHANNEL (%s), %s bytes\n", $rid, $bytes;
297             return;
298         }
299         my $chan = $channels[$index];
300         $chan->{$direction}{'seq'} += $bytes;
301         printf "ch%d (%s), %s bytes (%d--%d)\n", $index, $chan->{'id'}, $bytes,
302             $chan->{$direction}{'seq'}-$bytes, $chan->{$direction}{'seq'};
303         my @realdata = splice @$data, 0, $bytes;
304         if ($dumpdata) {
305             my $filekey = $direction . "file";
306             if (!defined $chan->{$filekey}) {
307                 my $filename = sprintf "ch%d.%s", $index, $direction;
308                 $chan->{$filekey} = FileHandle->new(">$filename");
309                 if (!defined $chan->{$filekey}) {
310                     die "$filename: $!\n";
311                 }
312             }
313             die "channel data not present in $seq\n" if @realdata < $bytes;
314             my $rawdata = pack "C*", @realdata;
315             my $fh = $chan->{$filekey};
316             print $fh $rawdata;
317         }
318         if (@realdata == $bytes and defined $chan->{$direction."data"}) {
319             my $rawdata = pack "C*", @realdata;
320             $chan->{$direction."data"}->($chan, $index, $direction, $rawdata);
321         }
322     },
323 #define SSH2_MSG_CHANNEL_EXTENDED_DATA            95    /* 0x5f */
324     'SSH2_MSG_CHANNEL_EXTENDED_DATA' => sub {
325         my ($direction, $seq, $data) = @_;
326         my ($rid, $type, $bytes) = &parse("uuu", $data);
327         if ($type == 1) {
328             $type = "SSH_EXTENDED_DATA_STDERR";
329         }
330         $rid = ($direction eq "i" ? "c" : "s") . $rid;
331         my $index = $chan_by_id{$rid};
332         if (!defined $index) {
333             printf "UNKNOWN_CHANNEL (%s), type %s, %s bytes\n", $rid,
334                 $type, $bytes;
335             return;
336         }
337         my $chan = $channels[$index];
338         $chan->{$direction}{'seq'} += $bytes;
339         printf "ch%d (%s), type %s, %s bytes (%d--%d)\n", $index,$chan->{'id'},
340             $type, $bytes, $chan->{$direction}{'seq'}-$bytes,
341             $chan->{$direction}{'seq'};
342         my @realdata = splice @$data, 0, $bytes;
343         if ($dumpdata) {
344             # We treat EXTENDED_DATA as equivalent to DATA, for the
345             # moment. It's not clear what else would be a better thing
346             # to do with it, and this at least is the Right Answer if
347             # the data is going to a terminal and the aim is to debug
348             # the terminal emulator.
349             my $filekey = $direction . "file";
350             if (!defined $chan->{$filekey}) {
351                 my $filename = sprintf "ch%d.%s", $index, $direction;
352                 $chan->{$filekey} = FileHandle->new(">$filename");
353                 if (!defined $chan->{$filekey}) {
354                     die "$filename: $!\n";
355                 }
356             }
357             die "channel data not present in $seq\n" if @realdata < $bytes;
358             my $rawdata = pack "C*", @realdata;
359             my $fh = $chan->{$filekey};
360             print $fh $rawdata;
361         }
362         if (@realdata == $bytes and defined $chan->{$direction."data"}) {
363             my $rawdata = pack "C*", @realdata;
364             $chan->{$direction."data"}->($chan, $index, $direction, $rawdata);
365         }
366     },
367 #define SSH2_MSG_CHANNEL_EOF                      96    /* 0x60 */
368     'SSH2_MSG_CHANNEL_EOF' => sub {
369         my ($direction, $seq, $data) = @_;
370         my ($rid) = &parse("uu", $data);
371         $rid = ($direction eq "i" ? "c" : "s") . $rid;
372         my $index = $chan_by_id{$rid};
373         if (!defined $index) {
374             printf "UNKNOWN_CHANNEL (%s)\n", $rid;
375             return;
376         }
377         my $chan = $channels[$index];
378         printf "ch%d (%s)\n", $index, $chan->{'id'};
379     },
380 #define SSH2_MSG_CHANNEL_CLOSE                    97    /* 0x61 */
381     'SSH2_MSG_CHANNEL_CLOSE' => sub {
382         my ($direction, $seq, $data) = @_;
383         my ($rid) = &parse("uu", $data);
384         $rid = ($direction eq "i" ? "c" : "s") . $rid;
385         my $index = $chan_by_id{$rid};
386         if (!defined $index) {
387             printf "UNKNOWN_CHANNEL (%s)\n", $rid;
388             return;
389         }
390         my $chan = $channels[$index];
391         $chan->{'state'} = ($chan->{'state'} eq "open" ? "halfclosed" :
392                             $chan->{'state'} eq "halfclosed" ? "closed" :
393                             "confused");
394         if ($chan->{'state'} eq "closed") {
395             $chan->{'ifile'}->close if defined $chan->{'ifile'};
396             $chan->{'ofile'}->close if defined $chan->{'ofile'};
397         }
398         printf "ch%d (%s)\n", $index, $chan->{'id'};
399     },
400 #define SSH2_MSG_CHANNEL_REQUEST                  98    /* 0x62 */
401     'SSH2_MSG_CHANNEL_REQUEST' => sub {
402         my ($direction, $seq, $data) = @_;
403         my ($rid, $type, $wantreply) = &parse("usb", $data);
404         $rid = ($direction eq "i" ? "c" : "s") . $rid;
405         my $index = $chan_by_id{$rid};
406         my $chan;
407         if (!defined $index) {
408             printf "UNKNOWN_CHANNEL (%s) %s (%s)", $rid,
409                 $type, $wantreply eq "yes" ? "reply" : "noreply";
410         } else {
411             $chan = $channels[$index];
412             printf "ch%d (%s) %s (%s)", $index, $chan->{'id'},
413                 $type, $wantreply eq "yes" ? "reply" : "noreply";
414             push @{$chan->{'requests_'.$direction}}, [$seq, $type]
415                 if $wantreply eq "yes";
416         }
417         if ($type eq "pty-req") {
418             my ($term, $w, $h, $pw, $ph, $modes) = &parse("suuuus", $data);
419             printf " %s %sx%s", &str($term), $w, $h;
420         } elsif ($type eq "x11-req") {
421             my ($single, $xprot, $xcookie, $xscreen) = &parse("bssu", $data);
422             print " one-off" if $single eq "yes";
423             printf " %s :%s", $xprot, $xscreen;
424         } elsif ($type eq "exec") {
425             my ($command) = &parse("s", $data);
426             printf " %s", &str($command);
427         } elsif ($type eq "subsystem") {
428             my ($subsys) = &parse("s", $data);
429             printf " %s", &str($subsys);
430             if ($subsys eq "sftp") {
431                 &sftp_setup($index);
432             }
433         } elsif ($type eq "window-change") {
434             my ($w, $h, $pw, $ph) = &parse("uuuu", $data);
435             printf " %sx%s", $w, $h;
436         } elsif ($type eq "xon-xoff") {
437             my ($can) = &parse("b", $data);
438             printf " %s", $can;
439         } elsif ($type eq "signal") {
440             my ($sig) = &parse("s", $data);
441             printf " %s", &str($sig);
442         } elsif ($type eq "exit-status") {
443             my ($status) = &parse("u", $data);
444             printf " %s", $status;
445         } elsif ($type eq "exit-signal") {
446             my ($sig, $core, $error, $lang) = &parse("sbss", $data);
447             printf " %s", &str($sig);
448             print " (core dumped)" if $core eq "yes";
449         }
450         print "\n";
451     },
452 #define SSH2_MSG_CHANNEL_SUCCESS                  99    /* 0x63 */
453     'SSH2_MSG_CHANNEL_SUCCESS' => sub {
454         my ($direction, $seq, $data) = @_;
455         my ($rid) = &parse("uu", $data);
456         $rid = ($direction eq "i" ? "c" : "s") . $rid;
457         my $index = $chan_by_id{$rid};
458         if (!defined $index) {
459             printf "UNKNOWN_CHANNEL (%s)\n", $rid;
460             return;
461         }
462         my $chan = $channels[$index];
463         printf "ch%d (%s)", $index, $chan->{'id'};
464         my $otherdir = ($direction eq "i" ? "o" : "i");
465         my $request = shift @{$chan->{'requests_' . $otherdir}};
466         if (defined $request) {
467             printf " to %s", $request->[0];
468         } else {
469             print " (spurious?)";
470         }
471         print "\n";
472     },
473 #define SSH2_MSG_CHANNEL_FAILURE                  100   /* 0x64 */
474     'SSH2_MSG_CHANNEL_FAILURE' => sub {
475         my ($direction, $seq, $data) = @_;
476         my ($rid) = &parse("uu", $data);
477         $rid = ($direction eq "i" ? "c" : "s") . $rid;
478         my $index = $chan_by_id{$rid};
479         if (!defined $index) {
480             printf "UNKNOWN_CHANNEL (%s)\n", $rid;
481             return;
482         }
483         my $chan = $channels[$index];
484         printf "ch%d (%s)", $index, $chan->{'id'};
485         my $otherdir = ($direction eq "i" ? "o" : "i");
486         my $request = shift @{$chan->{'requests_' . $otherdir}};
487         if (defined $request) {
488             printf " to %s", $request->[0];
489         } else {
490             print " (spurious?)";
491         }
492         print "\n";
493     },
494 #define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE               60
495     'SSH2_MSG_USERAUTH_GSSAPI_RESPONSE' => sub {
496         my ($direction, $seq, $data) = @_;
497         print "\n";
498     },
499 #define SSH2_MSG_USERAUTH_GSSAPI_TOKEN                  61
500     'SSH2_MSG_USERAUTH_GSSAPI_TOKEN' => sub {
501         my ($direction, $seq, $data) = @_;
502         print "\n";
503     },
504 #define SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE      63
505     'SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE' => sub {
506         my ($direction, $seq, $data) = @_;
507         print "\n";
508     },
509 #define SSH2_MSG_USERAUTH_GSSAPI_ERROR                  64
510     'SSH2_MSG_USERAUTH_GSSAPI_ERROR' => sub {
511         my ($direction, $seq, $data) = @_;
512         print "\n";
513     },
514 #define SSH2_MSG_USERAUTH_GSSAPI_ERRTOK                 65
515     'SSH2_MSG_USERAUTH_GSSAPI_ERRTOK' => sub {
516         my ($direction, $seq, $data) = @_;
517         print "\n";
518     },
519 #define SSH2_MSG_USERAUTH_GSSAPI_MIC                    66
520     'SSH2_MSG_USERAUTH_GSSAPI_MIC' => sub {
521         my ($direction, $seq, $data) = @_;
522         print "\n";
523     },
524 );
525
526 my %sftp_packets = (
527 #define SSH_FXP_INIT                              1     /* 0x1 */
528     0x1 => sub {
529         my ($chan, $index, $direction, $id, $data) = @_;
530         my ($ver) = &parse("u", $data);
531         printf "SSH_FXP_INIT %d\n", $ver;
532     },
533 #define SSH_FXP_VERSION                           2     /* 0x2 */
534     0x2 => sub {
535         my ($chan, $index, $direction, $id, $data) = @_;
536         my ($ver) = &parse("u", $data);
537         printf "SSH_FXP_VERSION %d\n", $ver;
538     },
539 #define SSH_FXP_OPEN                              3     /* 0x3 */
540     0x3 => sub {
541         my ($chan, $index, $direction, $id, $data) = @_;
542         my ($reqid, $path, $pflags) = &parse("usu", $data);
543         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_OPEN");
544         printf " \"%s\" ", $path;
545         if ($pflags eq 0) {
546             print "0";
547         } else {
548             my $sep = "";
549             if ($pflags & 1) { $pflags ^= 1; print "${sep}READ"; $sep = "|"; }
550             if ($pflags & 2) { $pflags ^= 2; print "${sep}WRITE"; $sep = "|"; }
551             if ($pflags & 4) { $pflags ^= 4; print "${sep}APPEND"; $sep = "|"; }
552             if ($pflags & 8) { $pflags ^= 8; print "${sep}CREAT"; $sep = "|"; }
553             if ($pflags & 16) { $pflags ^= 16; print "${sep}TRUNC"; $sep = "|"; }
554             if ($pflags & 32) { $pflags ^= 32; print "${sep}EXCL"; $sep = "|"; }
555             if ($pflags) { print "${sep}${pflags}"; }
556         }
557         print "\n";
558     },
559 #define SSH_FXP_CLOSE                             4     /* 0x4 */
560     0x4 => sub {
561         my ($chan, $index, $direction, $id, $data) = @_;
562         my ($reqid, $handle) = &parse("us", $data);
563         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_CLOSE");
564         printf " \"%s\"", &stringescape($handle);
565         print "\n";
566     },
567 #define SSH_FXP_READ                              5     /* 0x5 */
568     0x5 => sub {
569         my ($chan, $index, $direction, $id, $data) = @_;
570         my ($reqid, $handle, $offset, $len) = &parse("usUu", $data);
571         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_READ");
572         printf " \"%s\" %d %d", &stringescape($handle), $offset, $len;
573         print "\n";
574     },
575 #define SSH_FXP_WRITE                             6     /* 0x6 */
576     0x6 => sub {
577         my ($chan, $index, $direction, $id, $data) = @_;
578         my ($reqid, $handle, $offset, $wdata) = &parse("usUs", $data);
579         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_WRITE");
580         printf " \"%s\" %d [%d bytes]", &stringescape($handle), $offset, length $wdata;
581         print "\n";
582     },
583 #define SSH_FXP_LSTAT                             7     /* 0x7 */
584     0x7 => sub {
585         my ($chan, $index, $direction, $id, $data) = @_;
586         my ($reqid, $path) = &parse("us", $data);
587         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_LSTAT");
588         printf " \"%s\"", $path;
589         print "\n";
590     },
591 #define SSH_FXP_FSTAT                             8     /* 0x8 */
592     0x8 => sub {
593         my ($chan, $index, $direction, $id, $data) = @_;
594         my ($reqid, $handle) = &parse("us", $data);
595         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_FSTAT");
596         printf " \"%s\"", &stringescape($handle);
597         print "\n";
598     },
599 #define SSH_FXP_SETSTAT                           9     /* 0x9 */
600     0x9 => sub {
601         my ($chan, $index, $direction, $id, $data) = @_;
602         my ($reqid, $path) = &parse("us", $data);
603         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_SETSTAT");
604         my $attrs = &sftp_parse_attrs($data);
605         printf " \"%s\" %s", $path, $attrs;
606         print "\n";
607     },
608 #define SSH_FXP_FSETSTAT                          10    /* 0xa */
609     0xa => sub {
610         my ($chan, $index, $direction, $id, $data) = @_;
611         my ($reqid, $handle) = &parse("us", $data);
612         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_FSETSTAT");
613         my $attrs = &sftp_parse_attrs($data);
614         printf " \"%s\" %s", &stringescape($handle), $attrs;
615         print "\n";
616     },
617 #define SSH_FXP_OPENDIR                           11    /* 0xb */
618     0xb => sub {
619         my ($chan, $index, $direction, $id, $data) = @_;
620         my ($reqid, $path) = &parse("us", $data);
621         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_OPENDIR");
622         printf " \"%s\"", $path;
623         print "\n";
624     },
625 #define SSH_FXP_READDIR                           12    /* 0xc */
626     0xc => sub {
627         my ($chan, $index, $direction, $id, $data) = @_;
628         my ($reqid, $handle) = &parse("us", $data);
629         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_READDIR");
630         printf " \"%s\"", &stringescape($handle);
631         print "\n";
632     },
633 #define SSH_FXP_REMOVE                            13    /* 0xd */
634     0xd => sub {
635         my ($chan, $index, $direction, $id, $data) = @_;
636         my ($reqid, $path) = &parse("us", $data);
637         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_REMOVE");
638         printf " \"%s\"", $path;
639         print "\n";
640     },
641 #define SSH_FXP_MKDIR                             14    /* 0xe */
642     0xe => sub {
643         my ($chan, $index, $direction, $id, $data) = @_;
644         my ($reqid, $path) = &parse("us", $data);
645         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_MKDIR");
646         printf " \"%s\"", $path;
647         print "\n";
648     },
649 #define SSH_FXP_RMDIR                             15    /* 0xf */
650     0xf => sub {
651         my ($chan, $index, $direction, $id, $data) = @_;
652         my ($reqid, $path) = &parse("us", $data);
653         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_RMDIR");
654         printf " \"%s\"", $path;
655         print "\n";
656     },
657 #define SSH_FXP_REALPATH                          16    /* 0x10 */
658     0x10 => sub {
659         my ($chan, $index, $direction, $id, $data) = @_;
660         my ($reqid, $path) = &parse("us", $data);
661         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_REALPATH");
662         printf " \"%s\"", $path;
663         print "\n";
664     },
665 #define SSH_FXP_STAT                              17    /* 0x11 */
666     0x11 => sub {
667         my ($chan, $index, $direction, $id, $data) = @_;
668         my ($reqid, $path) = &parse("us", $data);
669         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_STAT");
670         printf " \"%s\"", $path;
671         print "\n";
672     },
673 #define SSH_FXP_RENAME                            18    /* 0x12 */
674     0x12 => sub {
675         my ($chan, $index, $direction, $id, $data) = @_;
676         my ($reqid, $srcpath, $dstpath) = &parse("uss", $data);
677         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_RENAME");
678         printf " \"%s\" \"%s\"", $srcpath, $dstpath;
679         print "\n";
680     },
681 #define SSH_FXP_STATUS                            101   /* 0x65 */
682     0x65 => sub {
683         my ($chan, $index, $direction, $id, $data) = @_;
684         my ($reqid, $status) = &parse("uu", $data);
685         &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_STATUS");
686         print " ";
687         if ($status eq "0") { print "SSH_FX_OK"; }
688         elsif ($status eq "1") { print "SSH_FX_EOF"; }
689         elsif ($status eq "2") { print "SSH_FX_NO_SUCH_FILE"; }
690         elsif ($status eq "3") { print "SSH_FX_PERMISSION_DENIED"; }
691         elsif ($status eq "4") { print "SSH_FX_FAILURE"; }
692         elsif ($status eq "5") { print "SSH_FX_BAD_MESSAGE"; }
693         elsif ($status eq "6") { print "SSH_FX_NO_CONNECTION"; }
694         elsif ($status eq "7") { print "SSH_FX_CONNECTION_LOST"; }
695         elsif ($status eq "8") { print "SSH_FX_OP_UNSUPPORTED"; }
696         else { printf "[unknown status %d]", $status; }
697         print "\n";
698     },
699 #define SSH_FXP_HANDLE                            102   /* 0x66 */
700     0x66 => sub {
701         my ($chan, $index, $direction, $id, $data) = @_;
702         my ($reqid, $handle) = &parse("us", $data);
703         &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_HANDLE");
704         printf " \"%s\"", &stringescape($handle);
705         print "\n";
706     },
707 #define SSH_FXP_DATA                              103   /* 0x67 */
708     0x67 => sub {
709         my ($chan, $index, $direction, $id, $data) = @_;
710         my ($reqid, $retdata) = &parse("us", $data);
711         &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_DATA");
712         printf " [%d bytes]", length $retdata;
713         print "\n";
714     },
715 #define SSH_FXP_NAME                              104   /* 0x68 */
716     0x68 => sub {
717         my ($chan, $index, $direction, $id, $data) = @_;
718         my ($reqid, $count) = &parse("uu", $data);
719         &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_NAME");
720         for my $i (1..$count) {
721             my ($name, $longname) = &parse("ss", $data);
722             my $attrs = &sftp_parse_attrs($data);
723             print " [name=\"$name\", longname=\"$longname\", attrs=$attrs]";
724         }
725         print "\n";
726     },
727 #define SSH_FXP_ATTRS                             105   /* 0x69 */
728     0x69 => sub {
729         my ($chan, $index, $direction, $id, $data) = @_;
730         my ($reqid) = &parse("u", $data);
731         &sftp_logreply($chan, $direction, $reqid, $id, "SSH_FXP_ATTRS");
732         my $attrs = &sftp_parse_attrs($data);
733         printf " %s", $attrs;
734         print "\n";
735     },
736 #define SSH_FXP_EXTENDED                          200   /* 0xc8 */
737     0xc8 => sub {
738         my ($chan, $index, $direction, $id, $data) = @_;
739         my ($reqid, $type) = &parse("us", $data);
740         &sftp_logreq($chan, $direction, $reqid, $id, "SSH_FXP_EXTENDED");
741         printf " \"%s\"", $type;
742         print "\n";
743     },
744 #define SSH_FXP_EXTENDED_REPLY                    201   /* 0xc9 */
745     0xc9 => sub {
746         my ($chan, $index, $direction, $id, $data) = @_;
747         my ($reqid) = &parse("u", $data);
748         print "\n";
749         &sftp_logreply($chan, $direction, $reqid,$id,"SSH_FXP_EXTENDED_REPLY");
750     },
751 );
752
753 my ($direction, $seq, $ourseq, $type, $data, $recording);
754 my %ourseqs = ('i'=>0, 'o'=>0);
755
756 $recording = 0;
757 while (<>) {
758     if ($recording) {
759         if (/^  [0-9a-fA-F]{8}  ((?:[0-9a-fA-F]{2} )*[0-9a-fA-F]{2})/) {
760             push @$data, map { $_ eq "XX" ? -1 : hex $_ } split / /, $1;
761         } else {
762             $recording = 0;
763             my $fullseq = "$direction$ourseq";
764             print "$fullseq: $type ";
765             if (defined $packets{$type}) {
766                 $packets{$type}->($direction, $fullseq, $data);
767             } else {
768                 printf "raw %s\n", join "", map { sprintf "%02x", $_ } @$data;
769             }
770         }
771     }
772     if (/^(Incoming|Outgoing) packet #0x([0-9a-fA-F]+), type \d+ \/ 0x[0-9a-fA-F]+ \((.*)\)/) {
773         $direction = ($1 eq "Incoming" ? 'i' : 'o');
774         # $seq is the sequence number quoted in the log file. $ourseq
775         # is our own count of the sequence number, which differs in
776         # that it shouldn't wrap at 2^32, should anyone manage to run
777         # this script over such a huge log file.
778         $seq = hex $2;
779         $ourseq = $ourseqs{$direction}++;
780         $type = $3;
781         $data = [];
782         $recording = 1;
783     }
784 }
785
786 if ($dumpchannels) {
787     my %stateorder = ('closed'=>0, 'rejected'=>1,
788                       'halfclosed'=>2, 'open'=>3, 'halfopen'=>4);
789     for my $index (0..$#channels) {
790         my $chan = $channels[$index];
791         my $so = $stateorder{$chan->{'state'}};
792         $so = 1000 unless defined $so; # any state I've missed above comes last
793         $chan->{'index'} = sprintf "ch%d", $index;
794         $chan->{'order'} = sprintf "%08d %08d", $so, $index;
795     }
796     my @sortedchannels = sort { $a->{'order'} cmp $b->{'order'} } @channels;
797     for my $chan (@sortedchannels) {
798         printf "%s (%s): %s\n", $chan->{'index'}, $chan->{'id'}, $chan->{'state'};
799     }
800 }
801
802 sub parseone {
803     my ($type, $data) = @_;
804     if ($type eq "u") { # uint32
805         my @bytes = splice @$data, 0, 4;
806         return "<missing>" if @bytes < 4 or grep { $_<0 } @bytes;
807         return unpack "N", pack "C*", @bytes;
808     } elsif ($type eq "U") { # uint64
809         my @bytes = splice @$data, 0, 8;
810         return "<missing>" if @bytes < 8 or grep { $_<0 } @bytes;
811         my @words = unpack "NN", pack "C*", @bytes;
812         return ($words[0] << 32) + $words[1];
813     } elsif ($type eq "b") { # boolean
814         my $byte = shift @$data;
815         return "<missing>" if !defined $byte or $byte < 0;
816         return $byte ? "yes" : "no";
817     } elsif ($type eq "B") { # byte
818         my $byte = shift @$data;
819         return "<missing>" if !defined $byte or $byte < 0;
820         return $byte;
821     } elsif ($type eq "s" or $type eq "m") { # string, mpint
822         my @bytes = splice @$data, 0, 4;
823         return "<missing>" if @bytes < 4 or grep { $_<0 } @bytes;
824         my $len = unpack "N", pack "C*", @bytes;
825         @bytes = splice @$data, 0, $len;
826         return "<missing>" if @bytes < $len or grep { $_<0 } @bytes;
827         if ($type eq "mpint") {
828             my $str = "";
829             if ($bytes[0] >= 128) {
830                 # Take two's complement.
831                 @bytes = map { 0xFF ^ $_ } @bytes;
832                 for my $i (reverse 0..$#bytes) {
833                     if ($bytes[$i] < 0xFF) {
834                         $bytes[$i]++;
835                         last;
836                     } else {
837                         $bytes[$i] = 0;
838                     }
839                 }
840                 $str = "-";
841             }
842             $str .= "0x" . join "", map { sprintf "%02x", $_ } @bytes;
843             return $str;
844         } else {
845             return pack "C*", @bytes;
846         }
847     }
848 }
849
850 sub parse {
851     my ($template, $data) = @_;
852     return map { &parseone($_, $data) } split //, $template;
853 }
854
855 sub str {
856     # Quote as a string. If I get enthusiastic I might arrange for
857     # strange characters inside the string to be quoted.
858     my $str = shift @_;
859     return "'$str'";
860 }
861
862 sub sftp_setup {
863     my $index = shift @_;
864     my $chan = $channels[$index];
865     $chan->{'obuf'} = $chan->{'ibuf'} = '';
866     $chan->{'ocnt'} = $chan->{'icnt'} = 0;
867     $chan->{'odata'} = $chan->{'idata'} = \&sftp_data;
868     $chan->{'sftpreqs'} = {};
869 }
870
871 sub sftp_data {
872     my ($chan, $index, $direction, $data) = @_;
873     my $buf = \$chan->{$direction."buf"};
874     my $cnt = \$chan->{$direction."cnt"};
875     $$buf .= $data;
876     while (length $$buf >= 4) {
877         my $msglen = unpack "N", $$buf;
878         last if length $$buf < 4 + $msglen;
879         my $msg = substr $$buf, 4, $msglen;
880         $$buf = substr $$buf, 4 + $msglen;
881         $msg = [unpack "C*", $msg];
882         my $type = shift @$msg;
883         my $id = sprintf "ch%d_sftp_%s%d", $index, $direction, ${$cnt}++;
884         print "$id: ";
885         if (defined $sftp_packets{$type}) {
886             $sftp_packets{$type}->($chan, $index, $direction, $id, $msg);
887         } else {
888             printf "unknown SFTP packet type %d\n", $type;
889         }
890     }
891 }
892
893 sub sftp_logreq {
894     my ($chan, $direction, $reqid, $id, $name) = @_;
895     print "$name";
896     if ($direction eq "o") { # requests coming _in_ are too weird to track
897         $chan->{'sftpreqs'}->{$reqid} = $id;
898     }
899 }
900
901 sub sftp_logreply {
902     my ($chan, $direction, $reqid, $id, $name) = @_;
903     print "$name";
904     if ($direction eq "i") { # replies going _out_ are too weird to track
905         if (defined $chan->{'sftpreqs'}->{$reqid}) {
906             print " to ", $chan->{'sftpreqs'}->{$reqid};
907             $chan->{'sftpreqs'}->{$reqid} = undef;
908         }
909     }
910 }
911
912 sub sftp_parse_attrs {
913     my ($data) = @_;
914     my ($flags) = &parse("u", $data);
915     return $flags if $flags eq "<missing>";
916     my $out = "{";
917     my $sep = "";
918     if ($flags & 0x00000001) { # SSH_FILEXFER_ATTR_SIZE
919         $out .= $sep . sprintf "size=%d", &parse("U", $data);
920         $sep = ", ";
921     }
922     if ($flags & 0x00000002) { # SSH_FILEXFER_ATTR_UIDGID
923         $out .= $sep . sprintf "uid=%d", &parse("u", $data);
924         $out .= $sep . sprintf "gid=%d", &parse("u", $data);
925         $sep = ", ";
926     }
927     if ($flags & 0x00000004) { # SSH_FILEXFER_ATTR_PERMISSIONS
928         $out .= $sep . sprintf "perms=%#o", &parse("u", $data);
929         $sep = ", ";
930     }
931     if ($flags & 0x00000008) { # SSH_FILEXFER_ATTR_ACMODTIME
932         $out .= $sep . sprintf "atime=%d", &parse("u", $data);
933         $out .= $sep . sprintf "mtime=%d", &parse("u", $data);
934         $sep = ", ";
935     }
936     if ($flags & 0x80000000) { # SSH_FILEXFER_ATTR_EXTENDED
937         my $extcount = &parse("u", $data);
938         while ($extcount-- > 0) {
939             $out .= $sep . sprintf "\"%s\"=\"%s\"", &parse("ss", $data);
940             $sep = ", ";
941         }
942     }
943     $out .= "}";
944     return $out;
945 }
946
947 sub stringescape {
948     my ($str) = @_;
949     $str =~ s!\\!\\\\!g;
950     $str =~ s![^ -~]!sprintf "\\x%02X", ord $&!eg;
951     return $str;
952 }