#! /usr/bin/perl # # simple fbox recovery tool # # Copyright (C) 2004 Enrik Berkhan # Copyright (C) 2006 Daniel Eiband # # 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 Socket; use Getopt::Std; use Errno qw(EEXIST); use Net::FTP; use Fcntl qw(SEEK_END SEEK_SET); use String::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 $packet = pack("vCCVNV", 0, 18, 1, 1, $setip, 0); our $packet = pack("vCCVNV", 0, 18, 1, 1, 0, 0); our $broadcast = sockaddr_in(5035, INADDR_BROADCAST); print "Looking for Fritz!Box "; $SIG{"ALRM"} = sub { return if --$scanning <= 0; if (scalar @boxes == 0) { $probe->send($packet, 0, $broadcast); print "o"; } else { print "."; } }; $scanning = 50; $probe->send($packet, 0, $broadcast); print "o"; while($scanning) { my $reply; alarm(1); if (my $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); } } } 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"; } # set ip now if differing! $ipaddr = (@{$boxes[0]})[4]; { 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; } # data_cmd ueberschreiben, da die Verbindung sonst zu spaet erfolgt! sub _data_cmd { my $ftp = shift; my $cmd = uc shift; my $ok = 1; my $where = delete ${*$ftp}{'net_ftp_rest'} || 0; my $arg; for $arg (@_) { croak("Bad argument '$arg'\n") if $arg =~ /[\r\n]/s; } if(${*$ftp}{'net_ftp_passive'} && !defined ${*$ftp}{'net_ftp_pasv'} && !defined ${*$ftp}{'net_ftp_port'}) { my $data = undef; $ok = defined $ftp->pasv; $ok = $ftp->_REST($where) if $ok && $where; if($ok) { # zuerst verbinden! $data = $ftp->_dataconn(); $ftp->command($cmd,@_); $ok = Net::FTP::CMD_INFO == $ftp->response(); if($ok) { $data->reading if $data && $cmd =~ /RETR|LIST|NLST/; return $data } $data->_close if $data; } return undef; } $ok = $ftp->port unless (defined ${*$ftp}{'net_ftp_port'} || defined ${*$ftp}{'net_ftp_pasv'}); $ok = $ftp->_REST($where) if $ok && $where; return undef unless $ok; $ftp->command($cmd,@_); return 1 if(defined ${*$ftp}{'net_ftp_pasv'}); $ok = CMD_INFO == $ftp->response(); return $ok unless exists ${*$ftp}{'net_ftp_intern_port'}; if($ok) { my $data = $ftp->_dataconn(); $data->reading if $data && $cmd =~ /RETR|LIST|NLST/; return $data; } close(delete ${*$ftp}{'net_ftp_listen'}); return undef; } } # passive mode geht mit Net::FTP nicht, connected zu spaet fuer ADAM2! # EVA kann nur passive mode $ftp = ADAM2FTP->new(inet_ntoa($ipaddr), Passive => 1, 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'); my @mtd0 = split(',', $ftp->getenv('mtd0'), 2); my @mtd1 = split(',', $ftp->getenv('mtd1'), 2); my @mtd2 = split(',', $ftp->getenv('mtd2'), 2); my @mtd3 = split(',', $ftp->getenv('mtd3'), 2); my @mtd4 = split(',', $ftp->getenv('mtd4'), 2); my $mtd0len = hex($mtd0[1]) - hex($mtd0[0]); my $mtd1len = hex($mtd1[1]) - hex($mtd1[0]); my $mtd2len = hex($mtd2[1]) - hex($mtd2[0]); my $mtd3len = hex($mtd3[1]) - hex($mtd3[0]); my $mtd4len = hex($mtd4[1]) - hex($mtd4[0]); print "Product ID: $pid\n"; print "Hardware Revision: $hwrev\n"; print "Urlader Revision: $ulrev\n"; print "Firmware Revision: $fwrev\n"; print "MTD0: $mtd0len bytes\n"; print "MTD1: $mtd1len bytes\n"; print "MTD2: $mtd2len bytes\n"; print "MTD3: $mtd3len bytes\n"; print "MTD4: $mtd4len bytes\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: $!" }; my $tar = "./tools/tar"; system("$tar xvf $opt{f} -C recover.tmp ./var/tmp/kernel.image ./var/tmp/filesystem.image"); if ($? >> 8) { die "$tar failed"; } elsif ($? & 127) { die "$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 && -s $mtd0) { $sum = remove_cksum($mtd0, $mtd0len); $opt{r} = 1; print "Flashing $mtd0 to mtd0 ..."; $ftp->put($mtd0, "mtd0"); if ($ftp->status() == $ftp->CMD_OK) { if (defined $sum) { my $flashsum = $ftp->check("mtd0"); if (defined $flashsum && $sum eq $flashsum) { print "checksum ok!\n"; } else { print "checksum bad! ($sum vs. $flashsum)\n"; } } print "success!\n"; } else { print "FAILED: ", $ftp->code(), " ", $ftp->message(), "\n"; $opt{r} = 0; } } if (-f $mtd1 && -s $mtd1) { $sum = remove_cksum($mtd1, $mtd1len); print "Flashing $mtd1 to mtd1 ..."; $ftp->put($mtd1, "mtd1"); if ($ftp->status() == $ftp->CMD_OK) { if (defined $sum) { my $flashsum = $ftp->check("mtd1"); if (defined $flashsum && $sum eq $flashsum) { print "checksum ok!\n"; } else { print "checksum bad! ($sum vs. $flashsum)\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}", "mtd4"); if ($ftp->status() == $ftp->CMD_OK) { print "success!\n"; $ftp->close(); exit; } else { print "FAILED: ", $ftp->code(), " ", $ftp->message(), "\n"; $opt{r} = 0; } } sleep(0.1); 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; }