553 lines
11 KiB
Perl
Executable File
553 lines
11 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
#
|
|
# Set PXELINUX hard-coded options
|
|
#
|
|
|
|
use Socket; # For gethostbyname
|
|
use Fcntl;
|
|
use bytes;
|
|
|
|
%option_names = (
|
|
6 => 'domain-name-servers',
|
|
15 => 'domain-name',
|
|
54 => 'next-server',
|
|
209 => 'config-file',
|
|
210 => 'path-prefix',
|
|
211 => 'reboottime'
|
|
);
|
|
|
|
@fmt_oneip = ("ip-address", \&parse_oneip, \&show_ip);
|
|
@fmt_multiip = ("ip-address-list", \&parse_multiip, \&show_ip);
|
|
@fmt_string = ("string", \&parse_string, \&show_string);
|
|
@fmt_uint32 = ("uint32", \&parse_uint32, \&show_uint32);
|
|
|
|
%option_format = (
|
|
6 => \@fmt_multiip,
|
|
15 => \@fmt_string,
|
|
54 => \@fmt_oneip,
|
|
67 => \@fmt_string,
|
|
209 => \@fmt_string,
|
|
210 => \@fmt_string,
|
|
211 => \@fmt_uint32
|
|
);
|
|
|
|
sub parse_oneip($)
|
|
{
|
|
my($s) = @_;
|
|
my($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($s);
|
|
|
|
return ($addrtype == AF_INET) ? $addrs[0] : undef;
|
|
}
|
|
|
|
sub parse_multiip($)
|
|
{
|
|
my($l) = @_;
|
|
my $s;
|
|
my @a = ();
|
|
my $addr;
|
|
my $d = '';
|
|
|
|
foreach $s (split(/,/, $l)) {
|
|
my($name,$aliases,$addrtype,$length,@addrs)
|
|
= gethostbyname($s);
|
|
if ($addrtype == AF_INET) {
|
|
foreach $addr (@addrs) {
|
|
$d .= $addr;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $d ne '' ? $d : undef;
|
|
}
|
|
|
|
sub show_ip($)
|
|
{
|
|
my($l) = @_;
|
|
|
|
if (length($l) & 3) {
|
|
return undef;
|
|
} else {
|
|
my @h = ();
|
|
my $i;
|
|
|
|
for ($i = 0; $i < length($l); $i += 4) {
|
|
push(@h, inet_ntoa(substr($l, $i, 4)));
|
|
}
|
|
|
|
return join(',', @h);
|
|
}
|
|
}
|
|
|
|
sub parse_string($)
|
|
{
|
|
return $_[0];
|
|
}
|
|
|
|
sub show_string($)
|
|
{
|
|
my($s) = @_;
|
|
my $o, $i, $c;
|
|
|
|
$o = "\'";
|
|
for ($i = 0; $i < length($s); $i++) {
|
|
$c = substr($s, $i, 1);
|
|
if ($c eq "\'" || $c eq '!') {
|
|
$o .= "\'\\$c\'";
|
|
} else {
|
|
$o .= $c;
|
|
}
|
|
}
|
|
$o .= "\'";
|
|
|
|
return $o;
|
|
}
|
|
|
|
sub parse_uint32($)
|
|
{
|
|
my($s) = @_;
|
|
|
|
if ($s =~ /^[0-9]+$/) {
|
|
return pack("N", $s);
|
|
} else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
sub show_uint32($)
|
|
{
|
|
my($l) = @_;
|
|
|
|
if (length($l) == 4) {
|
|
return unpack("N", $l);
|
|
} else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
sub parse_generic($)
|
|
{
|
|
my($s) = @_;
|
|
|
|
if ($s =~ /^[0-9a-f]{1,2}(:[0-9a-f]{1,2})*$/) {
|
|
my $h;
|
|
my @b = ();
|
|
|
|
foreach $h (split(/\:/, $s)) {
|
|
push(@b, hex $h);
|
|
}
|
|
|
|
return pack("C", @b);
|
|
} else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
sub show_generic($)
|
|
{
|
|
my($l) = @_;
|
|
my $i;
|
|
my @h;
|
|
|
|
for ($i = 0; $i < length($l); $i++) {
|
|
push(@h, sprintf("%02x", unpack("C", substr($l, $i, $1))));
|
|
}
|
|
|
|
return join(':', @h);
|
|
}
|
|
|
|
sub parse_option($$)
|
|
{
|
|
my($opt, $arg) = @_;
|
|
my $v;
|
|
|
|
if (defined($option_format{$opt})) {
|
|
$v = $option_format{$opt}[1]($arg);
|
|
return $v if (defined($v));
|
|
}
|
|
|
|
return parse_generic($arg);
|
|
}
|
|
|
|
sub show_option($$)
|
|
{
|
|
my($opt, $arg) = @_;
|
|
my $v;
|
|
|
|
if (defined($option_format{$opt})) {
|
|
$v = $option_format{$opt}[2]($arg);
|
|
return $v if (defined($v));
|
|
}
|
|
|
|
return show_generic($arg);
|
|
}
|
|
|
|
sub option_number($)
|
|
{
|
|
my($n) = @_;
|
|
|
|
if (defined($option_rnames{$n})) {
|
|
return $option_rnames{$n};
|
|
} elsif ($n =~ /^[0-9]+$/ && $n >= 1 && $n <= 254) {
|
|
return $n+0;
|
|
} else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
sub read_optsets($)
|
|
{
|
|
my($file) = @_;
|
|
my $data, $bdata, $adata;
|
|
my $patch_start = (stat($file))[7];
|
|
my $hdroffset = 0; # 0 means non-deep-embedded
|
|
my $bufsize = 0;
|
|
my $junk;
|
|
my %hdr;
|
|
|
|
return undef unless (seek($file, 0, SEEK_SET));
|
|
return undef unless (read($file, $data, 48) == 48);
|
|
|
|
my($mzmagic, $junk, $magic, $len, $flags, $boff, $blen, $aoff, $alen)
|
|
= unpack("va[6]VVVVVVV", $data);
|
|
|
|
if ($mzmagic == 0x5a4d) {
|
|
# It is an EFI file... search for the magic number
|
|
$hdroffset = 48;
|
|
my $magic = pack("VVVV", 0x2a171ead, 0x0600e65e,
|
|
0x4025a4e4, 0x42388fc8);
|
|
|
|
while (1) {
|
|
return undef unless (read($file, $data, 16) == 16);
|
|
last if ($data eq $magic);
|
|
|
|
$hdroffset += 16;
|
|
}
|
|
|
|
return undef unless (read($file, $data, 16) == 16);
|
|
($blen, $alen, $bufsize, $junk) = unpack("VVVV", $data);
|
|
|
|
$patch_start = $boff = $hdroffset + 32;
|
|
$aoff = $boff + $blen;
|
|
|
|
$hdr{'deep'} = 1;
|
|
$hdr{'bufsize'} = $bufsize;
|
|
$hdr{'hdroffset'} = $hdroffset;
|
|
} else {
|
|
# It is a BIOS PXE file
|
|
|
|
return undef if ($magic != 0x2983c8ac);
|
|
return undef if ($len < 7*4);
|
|
|
|
$hdr{'deep'} = 0;
|
|
}
|
|
|
|
if ($blen == 0) {
|
|
$bdata = '';
|
|
} else {
|
|
return undef unless (seek($file, $boff, SEEK_SET));
|
|
return undef unless (read($file, $bdata, $blen) == $blen);
|
|
$patch_start = $boff if ($boff < patch_start);
|
|
}
|
|
|
|
if ($alen == 0) {
|
|
$adata = '';
|
|
} else {
|
|
return undef unless (seek($file, $aoff, SEEK_SET));
|
|
return undef unless (read($file, $adata, $alen) == $alen);
|
|
$patch_start = $aoff if ($aoff < $patch_start);
|
|
}
|
|
|
|
$hdr{'patch_start'} = $patch_start;
|
|
|
|
return (\%hdr, $bdata, $adata);
|
|
}
|
|
|
|
sub write_optsets($$@)
|
|
{
|
|
my($file, $hdr, $bdata, $adata) = @_;
|
|
my $boff = 0;
|
|
my $aoff = 0;
|
|
my $bufsize = 0;
|
|
my $patch_start = $hdr->{'patch_start'};
|
|
my $len;
|
|
|
|
$bdata .= "\xff" unless ($bdata eq '');
|
|
$adata .= "\xff" unless ($adata eq '');
|
|
|
|
$len = length($bdata) + length($adata);
|
|
|
|
if (defined($hdr->{'bufsize'})) {
|
|
return undef unless ($len <= $hdr->{'bufsize'});
|
|
}
|
|
|
|
return undef unless (seek($file, $patch_start, SEEK_SET));
|
|
|
|
if (length($bdata)) {
|
|
$boff = $patch_start;
|
|
return undef unless (print $file $bdata);
|
|
$patch_start += length($bdata);
|
|
}
|
|
|
|
if (length($adata)) {
|
|
$aoff = $patch_start;
|
|
return undef unless (print $file $adata);
|
|
$patch_start += length($adata);
|
|
}
|
|
|
|
if ($hdr->{'deep'}) {
|
|
return undef unless (print $file "\0" x ($hdr->{'bufsize'} - $len));
|
|
return undef unless (seek($file, $hdr->{'hdroffset'} + 16, SEEK_SET));
|
|
my $hdr = pack("VV", length($bdata), length($adata));
|
|
return undef unless (print $file $hdr);
|
|
} else {
|
|
my $hdr = pack("VVVV", $boff, length($bdata), $aoff, length($adata));
|
|
|
|
return undef unless (seek($file, 8+3*4, SEEK_SET));
|
|
return undef unless (print $file $hdr);
|
|
|
|
truncate($file, $patch_start);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub delete_option($$)
|
|
{
|
|
my ($num, $block) = @_;
|
|
my $o, $l, $c, $x;
|
|
|
|
$x = 0;
|
|
while ($x < length($block)) {
|
|
($o, $l) = unpack("CC", substr($block, $x, 2));
|
|
if ($o == $num) {
|
|
# Delete this option
|
|
substr($block, $x, $l+2) = '';
|
|
} elsif ($o == 0) {
|
|
# Delete a null option
|
|
substr($block, $x, 1) = '';
|
|
} elsif ($o == 255) {
|
|
# End marker - truncate block
|
|
$block = substr($block, 0, $x);
|
|
last;
|
|
} else {
|
|
# Skip to the next option
|
|
$x += $l+2;
|
|
}
|
|
}
|
|
|
|
return $block;
|
|
}
|
|
|
|
sub add_option($$$)
|
|
{
|
|
my ($num, $data, $block) = @_;
|
|
|
|
$block = delete_option($num, $block);
|
|
|
|
if (length($data) == 0) {
|
|
return $block;
|
|
} elsif (length($data) > 255) {
|
|
die "$0: option $num has too much data (max 255 bytes)\n";
|
|
} else {
|
|
return $block . pack("CC", $num, length($data)) . $data;
|
|
}
|
|
}
|
|
|
|
sub list_options($$)
|
|
{
|
|
my($pfx, $data) = @_;
|
|
my $x, $o, $l;
|
|
|
|
while ($x < length($data)) {
|
|
($o, $l) = unpack("CC", substr($data, $x, 2));
|
|
|
|
if ($o == 0) {
|
|
$x++;
|
|
} elsif ($o == 255) {
|
|
last;
|
|
} else {
|
|
my $odata = substr($data, $x+2, $l);
|
|
last if (length($odata) != $l); # Incomplete option
|
|
|
|
printf "%s%-20s %s\n", $pfx,
|
|
$option_names{$o} || sprintf("%d", $o),
|
|
show_option($o, $odata);
|
|
|
|
$x += $l+2;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub usage()
|
|
{
|
|
my $i;
|
|
|
|
print STDERR "Usage: $0 options pxelinux.0\n";
|
|
print STDERR "Options:\n";
|
|
print STDERR "--before option value -b Add an option before DHCP data\n";
|
|
print STDERR "--after option value -a Add an option after DHCP data\n";
|
|
print STDERR "--delete option -d Delete an option\n";
|
|
print STDERR "--list -l List set options\n";
|
|
print STDERR "--dry-run -n Don't modify the target file\n";
|
|
print STDERR "--help -h Display this help text\n";
|
|
print STDERR "\n";
|
|
print STDERR "The following DHCP options are currently recognized:\n";
|
|
printf STDERR "%-23s %-3s %s\n", 'Name', 'Num', 'Value Format';
|
|
|
|
foreach $i (sort { $a <=> $b } keys(%option_names)) {
|
|
printf STDERR "%-23s %3d %s\n",
|
|
$option_names{$i}, $i, $option_format{$i}[0];
|
|
}
|
|
}
|
|
|
|
%option_rnames = ();
|
|
foreach $opt (keys(%option_names)) {
|
|
$option_rnames{$option_names{$opt}} = $opt;
|
|
}
|
|
|
|
%before = ();
|
|
%after = ();
|
|
@clear = ();
|
|
$usage = 0;
|
|
$err = 0;
|
|
$list = 0;
|
|
$no_write = 0;
|
|
undef $file;
|
|
|
|
while (defined($opt = shift(@ARGV))) {
|
|
if ($opt !~ /^-/) {
|
|
if (defined($file)) {
|
|
$err = $usage = 1;
|
|
last;
|
|
}
|
|
$file = $opt;
|
|
} elsif ($opt eq '-b' || $opt eq '--before') {
|
|
$oname = shift(@ARGV);
|
|
$odata = shift(@ARGV);
|
|
|
|
if (!defined($odata)) {
|
|
$err = $usage = 1;
|
|
last;
|
|
}
|
|
|
|
$onum = option_number($oname);
|
|
if (!defined($onum)) {
|
|
print STDERR "$0: unknown option name: $oname\n";
|
|
$err = 1;
|
|
next;
|
|
}
|
|
|
|
$odata = parse_option($onum, $odata);
|
|
if (!defined($odata)) {
|
|
print STDERR "$0: unable to parse data for option $oname\n";
|
|
$err = 1;
|
|
next;
|
|
}
|
|
|
|
delete $after{$onum};
|
|
$before{$onum} = $odata;
|
|
push(@clear, $onum);
|
|
} elsif ($opt eq '-a' || $opt eq '--after') {
|
|
$oname = shift(@ARGV);
|
|
$odata = shift(@ARGV);
|
|
|
|
if (!defined($odata)) {
|
|
$err = $usage = 1;
|
|
last;
|
|
}
|
|
|
|
$onum = option_number($oname);
|
|
if (!defined($onum)) {
|
|
print STDERR "$0: unknown option name: $oname\n";
|
|
$err = 1;
|
|
next;
|
|
}
|
|
|
|
$odata = parse_option($onum, $odata);
|
|
if (!defined($odata)) {
|
|
print STDERR "$0: unable to parse data for option $oname\n";
|
|
$err = 1;
|
|
next;
|
|
}
|
|
|
|
delete $before{$onum};
|
|
$after{$onum} = $odata;
|
|
push(@clear, $onum);
|
|
} elsif ($opt eq '-d' || $opt eq '--delete') {
|
|
$oname = shift(@ARGV);
|
|
|
|
if (!defined($oname)) {
|
|
$err = $usage = 1;
|
|
last;
|
|
}
|
|
|
|
$onum = option_number($oname);
|
|
if (!defined($onum)) {
|
|
print STDERR "$0: unknown option name: $oname\n";
|
|
$err = 1;
|
|
next;
|
|
}
|
|
|
|
push(@clear, $onum);
|
|
delete $before{$onum};
|
|
delete $after{$onum};
|
|
} elsif ($opt eq '-n' || $opt eq '--no-write' || $opt eq '--dry-run') {
|
|
$no_write = 1;
|
|
} elsif ($opt eq '-l' || $opt eq '--list') {
|
|
$list = 1;
|
|
} elsif ($opt eq '-h' || $opt eq '--help') {
|
|
$usage = 1;
|
|
} else {
|
|
print STDERR "Invalid option: $opt\n";
|
|
$err = $usage = 1;
|
|
}
|
|
}
|
|
|
|
if (!defined($file) && !$usage) {
|
|
$err = $usage = 1;
|
|
}
|
|
if ($usage) {
|
|
usage();
|
|
}
|
|
if ($err || $usage) {
|
|
exit($err);
|
|
}
|
|
|
|
if (!scalar(@clear)) {
|
|
$no_write = 1; # No modifications requested
|
|
}
|
|
|
|
$mode = $no_write ? '<' : '+<';
|
|
|
|
open(FILE, $mode, $file)
|
|
or die "$0: cannot open: $file: $!\n";
|
|
($hdrinfo, @data) = read_optsets(\*FILE);
|
|
if (!defined($hdrinfo)) {
|
|
die "$0: $file: patch block not found or file corrupt\n";
|
|
}
|
|
|
|
foreach $o (@clear) {
|
|
$data[0] = delete_option($o, $data[0]);
|
|
$data[1] = delete_option($o, $data[1]);
|
|
}
|
|
foreach $o (keys(%before)) {
|
|
$data[0] = add_option($o, $before{$o}, $data[0]);
|
|
}
|
|
foreach $o (keys(%after)) {
|
|
$data[1] = add_option($o, $after{$o}, $data[1]);
|
|
}
|
|
|
|
if ($list) {
|
|
list_options('-b ', $data[0]);
|
|
list_options('-a ', $data[1]);
|
|
}
|
|
|
|
if (!$no_write) {
|
|
if (!write_optsets(\*FILE, $hdrinfo, @data)) {
|
|
die "$0: $file: failed to write options: $!\n";
|
|
}
|
|
}
|
|
|
|
close(FILE);
|
|
exit 0;
|