#! /usr/bin/perl # # simple fbox recovery tool # # Copyright (C) 2004,2005 Enrik Berkhan # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # use IO::Socket::INET; use IO::Select; use Socket; use Getopt::Std; use Errno qw(EEXIST); use Net::FTP; use Fcntl qw(SEEK_END SEEK_SET); my $do_crc = 1; BEGIN { eval { require Compress::Zlib }; if ($@) { eval { require String::CRC32 }; if ($@) { print STDERR "No crc32() implementation available!\nInstall Compress::Zlib or String::CRC32 if you want CRC support.\n"; $do_crc = 0; } else { import String::CRC32 qw(crc32); } } else { import Compress::Zlib qw(crc32); } } use warnings; $| = 1; my %opt; getopts('hrf:i:l:s:u', \%opt); if ($opt{h}) { print "usage: recover -h\n"; print " recover [-l IPADDR] [-i IPADDR] [-f firmeware.image] [-r]\n"; print " recover [-l IPADDR] [-i IPADDR] -s image\n"; print " recover [-l IPADDR] [-i IPADDR] -u\n"; print " -i IPADDR: set boot time IP to IPADDR within environment\n"; print " -l IPADDR: choose local addr (for multihomed)\n"; print " -f image: extract fs and kernel from tarfile and flash\n"; print " -s image: boot (standalone) image directly from RAM\n"; print " -r: reboot fbox\n"; print " -u: update box with filesystem.img, kernel.img from cwd\n"; exit; } my $ipaddr; my $setip; if ($opt{i}) { $setip = unpack("N", inet_aton($opt{i})); } else { $setip = 0; } $opt{l} = "0.0.0.0" unless defined $opt{l}; my $probe = IO::Socket::INET->new(Proto => 'udp', Broadcast => 1, LocalAddr => $opt{l}, LocalPort => 5035) or die "socket: $!"; my $sel = IO::Select->new($probe); my $packet = pack("vCCVNV", 0, 18, 1, 1, 0, 0); my $broadcast = sockaddr_in(5035, INADDR_BROADCAST); $probe->send($packet, 0, $broadcast); scan_again: print "Looking for Fritz!Box "; @boxes = (); $scanning = 10; print "o"; while($scanning) { my $reply; my @ready; if (@ready = $sel->can_read(0.2)) { $peer = $probe->recv($reply, 16); next if (length($reply) < 16); my ($port, $addr) = sockaddr_in($peer); my ($major, $minor1, $minor2, $code, $addr2) = unpack("vCCVV", $reply); $addr2 = pack("N", $addr2); if ($code == 2) { print "O"; push @boxes, [$major, $minor1, $minor2, $addr, $addr2]; $scanning = 2 if ($scanning > 2); } } else { $scanning--; if (scalar @boxes == 0) { $probe->send($packet, 0, $broadcast); print "o"; } else { print "."; } } } if (scalar @boxes == 0) { print " none found, giving up.\n"; exit 1; } else { print " found!\n"; } foreach $box (@boxes) { my ($major, $minor1, $minor2, $addr, $addr2) = @$box; printf "ADAM2 version $major.$minor1.$minor2 at %s (%s)\n", inet_ntoa($addr), inet_ntoa($addr2); } if (scalar @boxes > 1) { print "more than one boxes: no work\n"; exit 1; } $ipaddr = (@{$boxes[0]})[4]; if (!$ipaddr && !$setip) { print "box has no ip, giving up\n"; exit 1; } if ($setip) { if ($setip != unpack('N', $ipaddr)) { print "setting ip address\n"; my $setpacket = pack("vCCVNV", 0, 18, 1, 1, $setip, 0); $probe->send($setpacket, 0, $broadcast); goto scan_again; } else { print "ip already ok\n"; } } { package ADAM2FTP; use base qw(Net::FTP); # ADAM2 requires upper case commands, some brain dead firewall doesn't ;-) sub _USER { shift->command("USER",@_)->response() } sub _PASV { shift->command("P\@SW")->response() == Net::FTP::CMD_OK } sub _GETENV { my $ftp = shift; my ($ok, $name, $value); $ftp->command("GETENV",@_); while(length($ok = $ftp->response()) < 1) { my $line = $ftp->getline(); unless (defined($value)) { chomp($line); ($name, $value) = split(/\s+/, $line, 2); } } $ftp->debug_print(0, "getenv: $value\n") if $ftp->debug(); return $value; } sub getenv { my $ftp = shift; my $name = shift; return $ftp->_GETENV($name); } sub _REBOOT { shift->command("REBOOT")->response() == Net::FTP::CMD_OK } sub reboot { my $ftp = shift; $ftp->_REBOOT; $ftp->close; } sub check { my $ftp = shift; delete ${*$ftp}{'net_ftp_port'}; delete ${*$ftp}{'net_ftp_pasv'}; my $data = $ftp->_data_cmd('CHECK' ,@_) or return undef; my $sum; if (${${*$ftp}{'net_cmd_resp'}}[0] =~ /^Flash check 0x([0-9A-F]{8})/) { $sum = hex($1); } $data->_close(); return $sum; } } # passive mode geht mit Net::FTP nicht, connected zu spaet fuer ADAM2! $ftp = ADAM2FTP->new(inet_ntoa($ipaddr), Passive => 0, Debug => 0, Timeout => 600) or die "can't FTP ADAM2"; $ftp->login("adam2", "adam2") or die "can't login adam2"; $ftp->binary(); my $pid = $ftp->getenv('ProductID'); my $hwrev = $ftp->getenv('HWRevision'); my $fwrev = $ftp->getenv('firmware_info'); my $ulrev = $ftp->getenv('urlader-version'); print "Product ID: $pid\n"; print "Hardware Revision: $hwrev\n"; print "Urlader Revision: $ulrev\n"; print "Firmware Revision: $fwrev\n"; $ftp->hash(\*STDOUT, 64 * 1024); $mtd0 = "filesystem.image"; $mtd1 = "kernel.image"; if ($opt{f}) { unless (-f $opt{f}) { die "$opt{f} not found\n"; } mkdir "recover.tmp"; if ($! && $! != EEXIST) { die "recover.tmp: $!" }; unless(substr($opt{f}, 0, 1) eq '/') { $opt{f} = "../$opt{f}"; } system("busybox tar xf $opt{f} -C recover.tmp ./var/tmp/kernel.image ./var/tmp/filesystem.image"); if ($? >> 8) { die "busybox tar failed"; } elsif ($? & 127) { die "busybox tar killed"; } chmod 0644, "recover.tmp/var/tmp/filesystem.image", "recover.tmp/var/tmp/kernel.image"; $mtd0 = "recover.tmp/var/tmp/filesystem.image"; $mtd1 = "recover.tmp/var/tmp/kernel.image"; } if ($opt{f} || $opt{u}) { $ftp->quot("MEDIA", "FLSH"); if (-f $mtd0) { $sum = remove_cksum($mtd0, 0x300000); $opt{r} = 1; print "Flashing $mtd0 to mtd0 ..."; $ftp->put($mtd0, "1 mtd0"); if ($ftp->status() == $ftp->CMD_OK) { if (defined $sum) { my $flashsum = $ftp->check("1 mtd0"); if (defined $flashsum && $sum eq $flashsum) { print "checksum ok!\n"; } else { print "checksum bad!\n"; } } print "success!\n"; } else { print "FAILED: ", $ftp->code(), " ", $ftp->message(), "\n"; $opt{r} = 0; } } if (-f $mtd1) { $sum = remove_cksum($mtd1, 0xb0000); print "Flashing $mtd1 to mtd1 ..."; $ftp->put($mtd1, "1 mtd1"); if ($ftp->status() == $ftp->CMD_OK) { if (defined $sum) { my $flashsum = $ftp->check("1 mtd1"); if (defined $flashsum && $sum eq $flashsum) { print "checksum ok!\n"; } else { print "checksum bad!\n"; } } print "success!\n"; } else { print "FAILED: ", $ftp->code(), " ", $ftp->message(), "\n"; $opt{r} = 0; } } } elsif ($opt{s}) { print "Sending and booting $opt{s}..."; $ftp->quot("MEDIA", "SDRAM"); $ftp->put("$opt{s}", "1 mtd4"); if ($ftp->status() == $ftp->CMD_OK) { print "success!\n"; $ftp->close(); exit; } else { print "FAILED: ", $ftp->code(), " ", $ftp->message(), "\n"; $opt{r} = 0; } } if ($opt{r}) { print "Rebooting...\n"; $ftp->reboot(); } else { $ftp->quit(); } END { if ($opt{f}) { unlink("recover.tmp/var/tmp/kernel.image"); unlink("recover.tmp/var/tmp/filesystem.image"); rmdir("recover.tmp/var/tmp"); rmdir("recover.tmp/var"); rmdir("recover.tmp"); } } sub remove_cksum { my $name = shift; my $mtdsize = shift; open F, "+<$name" or die "open: $!"; $size = (stat F)[7]; die "stat: $!" unless defined($size); if ($size >= 8) { seek F, -8, SEEK_END or die "seek: $!"; read F, $magic, 4; read F, $chksum, 4; if (unpack("V", $magic) == 0xC453DE23) { $size -= 8; truncate F, $size or die "truncate: $!"; print "$name: removed checksum\n"; } else { print "$name: no checksum\n"; } seek F, 0, SEEK_SET or die "seek: $!"; $chksum = crc32(*F); $chksum = crc32("\xff" x ($mtdsize - $size), $chksum); printf "CRC32: %08X\n", $chksum; return $chksum; } return undef; }