#!/usr/bin/perl -w
# $Header: /auto/homes/pb22/pl/RCS/hostid-fix.pl,v 1.60 2022/07/28 06:44:26 pb22 Exp $
# Use "pod2text" for info

=head1 NAME

hostid-fix - fix files relating to a host's identidy (e.g. after clone)

=head1 SYNOPSIS

B<hostid-fix.pl> [-n] [-v] [-a ] [--cron] [--boot] [--resume] [--update] [--changed] [--root path] [--src path] [--host name] [--ip ip[/len]] [--mac mac] [--user user] [--group group] [--uuid uuid] [--resetuuid]   [<tests> ...]

=head1 OPTIONS

=over 4

=item B<--verbose>

show what it's doing

=item B<--help>

generate help info.

=item B<-n>

do not actually do it

=item B<--changed>

the info is likely to have changed, so don't trust current info.

=item B<--root> I</path>

use I</path> as a prefix to all destination file names

=item B<--src> I</path>

use I</path> as a prefix to all source file names

=item B<--host> I<hostname>

Use I<hostname> as the host name. It mabe be a FQDN or an UQDN.

=item B<--mac> I<mac>

Use I<mac> as the host mac address

=item B<--ip> I<ip>[/I<len>]

Use I<ip> as the host ip address, with length I<len> (defaults to the env var IP_LEN or 20). Leading components of 128.232.0 may be omitted.  Is using netplan, it uses the env var NIC_NAME which defaults to eth0.

=item B<--user> I<user>

Set the user with special access in B</etc/group> to I<user> and change the ownership of B</etc/user-config/bundles> and  B</etc/user-config/patches>.

=item B<--group> I<group>

Set the group with special access in B</etc/sudoers> to I<user> and change the group of B</etc/user-config/bundles> and  B</etc/user-config/patches>.

=item B<--uuid> I<uuid>

Set the root FS UUID to I<uuid>

=item B<--resetuuid>

Set the root FS UUID to a 'random' value

=item B<--cron>

Perform actions expected by cron

=item B<--boot>

Perform actions expected on boot

=item B<--resume>

Perform actions expected on resume

=item B<--cron>

Perform actions expected by cron

=item B<--do_hosts>

Perform the B</etc/hosts> test that the localhost name are OK

=back

=head1 DESCRIPTION

fix a machine's ID after a clone, etc.

It does B<NOT> cope with B</etc/ssh/ssh_host_>I<*>.
It only checks B</etc/fstab> and GrUB files for B<UUID=> (ubuntu), not B<LABEL=> (redhat).
B</etc/mdadm/mdadm.conf> should be regenerated using "B<cl-asuser mdadm-E>".

It checks, but does not fix, B</etc/krb5.keytab>
unless B</etc/krb5.keytab->I<$host> exists, in which case it is renamed.

B</etc/resolv.conf> check that there is a B<search> and at least 3 B<nameserver> lines.

B</etc/ldap.conf> check B<pam_groupdn> is correct (and a plausible B<uri>)

B</etc/udev/rules.d/70-persistent-net.rules> check that the B<ATTR{address}> address is correct,

B</etc/network/interfaces> check B<address> for B<eth0> is set if it is B<static>

B</etc/sysconfig/network-scripts/ifcfg-eth0> check B<IPADDR> is set unless DHCP, and that B<HWADDR> is not..
Later systems use netplan.

B</etc/hostname> check the host name is a FQDN

B</etc/sysconfig/network> check B<HOSTNAME> is a FQDN

B</etc/group> should allow I<$user> to do the same things which B<localadmin>
(on ubuntu systems) can

B</etc/user-config/bundles>, B</etc/user-config/patches> and  B</etc/user-config/hostid> should be owned by
I<$user>

=cut

# ?  diff -r -x kera/etc/default/console-setup
# ?  < BOOTTIME_KMAP_MD5="e6fb35d87d5b3a6d2f1ecab11aaec757"
# ?  > BOOTTIME_KMAP_MD5="190dc876fc732ef9af4404abd28ab863"
# ?  
# ?  diff -r -x kera/etc/fstab
# ?  13c13
# ?  < UUID=3b1da7f4-efdf-4fac-b33b-5a546a834311 /               ext3    relatime,errors=remount-ro 0       1
# ?  > UUID=8c1d8118-5832-4dff-b72b-e3e37123bea1 /               ext3    relatime,errors=remount-ro 0       1
# ?  
# ?  diff -r -x kera/etc/mdadm/mdadm.conf
# ?  20c20
# ?  < ARRAY /dev/md3 level=raid1 num-devices=2 UUID=721ac7ef:be53e03f:a9b1892e:e7a21d73
# ?  > ARRAY /dev/md3 level=raid1 num-devices=2 UUID=67aa4532:0d0f89c0:716c1e32:1381dd92
# ?  
# ?  diff -r -x kera/etc/popularity-contest.conf
# ?  < MY_HOSTID="76cf74fbbdbc405296dc322068a104a1"
# ?  > MY_HOSTID="50d1987941a0451891b27ccde6019327"
# ?  < DAY="4"
# ?  > DAY="3"

# diff -r -x kera/etc/ldap.conf
# < pam_groupdn cn=kele,ou=HostGroups,dc=cl,dc=cam,dc=ac,dc=uk
# > pam_groupdn cn=kera,ou=HostGroups,dc=cl,dc=cam,dc=ac,dc=uk

# diff -r -x kera/etc/udev/rules.d/70-persistent-net.rules
# < SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:12:3f:85:90:26", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"
# > SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:12:3f:96:5b:fc", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"

# diff -r -x kera/etc/network/interfaces
# <       address 128.232.16.52
# >       address 128.232.16.53

# diff -r -x kera/etc/hostname
# < kele.cl.cam.ac.uk
# > kera

# diff -r -x kera/etc/udev/rules.d/70-persistent-cd.rules
# < # DVD-ROM_DDU1615 (pci-0000:00:1f.1-scsi-0:0:0:0)
# > # DVD-ROM_TS-H352C (pci-0000:00:1f.1-scsi-0:0:0:0)

# /etc/hosts: process:
# 127.0.1.1       bira.cl.cam.ac.uk       bira
# 127.0.0.1               localhost.localdomain toton
# 127.0.0.1       xie     localhost.localdomain   localhost


use Getopt::Long;
use Getopt::Long qw(:config require_order);
use Pod::Usage;
use Socket;

$DATA	= 1;
$EOF	= 2;
$root	= "";
$src	= "";

%opts = (
	"verbose|v+"	=> \$verbose,
        "help|h"	=> \$help,
	"changed"	=> \$changed,
	"host:s"	=> \$host,
	"root:s"	=> \$root,
	"src:s"		=> \$src,
	"user:s"	=> \$user,
	"group:s"	=> \$group,
	"mac:s"		=> \$mac,
	"ip:s"		=> \$ip,
	"aptsrc:s"	=> \$aptsrcu,
	"uuid:s"	=> \$uuid,
	"resetuuid"	=> \$resetuuid,
	"n"		=> \$n,
	"cron"		=> \$cron,
	"boot"		=> \$boot,
	"resume"	=> \$resume,
	"update"	=> \$update,
	"quietish"	=> \$quietish,
	"all|a"		=> \$all,

        "do_hosts!"	=> \$do_hosts,
        "do_resolv!"	=> \$do_resolv,
        "do_ldapconf!"	=> \$do_ldapconf,
        "do_udevnet!"	=> \$do_udevnet,
        "do_network!"	=> \$do_network,
        "do_hostname!"	=> \$do_hostname,
        "do_aptsrc!"	=> \$do_aptsrc,
        "do_group!"	=> \$do_group,
        "do_sudoers!"	=> \$do_sudoers,
        "do_aptcl!"	=> \$do_aptcl,
        "do_bundles!"	=> \$do_bundles,
        "do_patches!"	=> \$do_patches,
        "do_hostid!"	=> \$do_hostid,
        "do_keytab!"	=> \$do_keytab,
        "do_uuid!"	=> \$do_uuid,
        "do_sshk!"	=> \$do_sshk,
        "do_xensource!"	=> \$do_xensource,
);

$ENV{'PATH'} .= ":/sbin:/usr/sbin:/usr/kerberos/bin";

$aptsrcu = "http://mirror.apps.cam.ac.uk/pub/linux/ubuntu";
$aptsrcd = "http://mirror.pps.cam.ac.uk/pub/linux/debian";
$aptsrcd if 0; # FIX: use it ...
$hostid = "$root/etc/user-config/hostid";
$cloudc = ",ou=HostGroups,dc=cl,dc=cam,dc=ac,dc=uk";

use Socket qw(AF_INET6 AF_INET getnameinfo sockaddr_in6 sockaddr_in inet_pton NIx_NOSERV);
our $mghba_port = 0;
our $mghba_flags = 0;
our $mghba_xflags = NIx_NOSERV;

$trace  = $ENV{'TRACE'} || 0;

$verbose = 0;

%clone_uuid = (
	"ca178818-2f70-4e46-93f4-be1605deb653"	=> 1,	# "master" U12.04 LTS clone disk 2013
	"58956853-b55f-475c-bbe1-4177875228d5"	=> 1,	# "master" U14.04 LTS clone disk 2014
	"62bf5793-009e-4eae-8ab8-472bd79363e6"	=> 1,	# ACS 2012
);

%sshk_cloned = (
	"2b2ecfd404ef968991d30ed96b3f3ac1"	=> 1, # ssh_host_dsa_key.pub
	"e276f16773b1610be3de016ae4c1432e"	=> 1, # ssh_host_ecdsa_key.pub
	"0ddf2b812e6402ffcd8432d17b57574b"	=> 1, # ssh_host_key.pub
	"caf1ab5484c7ca337497b7cfddb79b61"	=> 1, # ssh_host_rsa_key.pub
	"4db35cb52942fc5c728d61ef3fe85e5e"	=> 1, # ssh_host_dsa_key
	"b8862890c637233f8700b8a6a92a6408"	=> 1, # ssh_host_ecdsa_key
	"b0d51a249e512d3defdfbe1173af18c6"	=> 1, # ssh_host_key
	"14b5bec3459e6b707a719bdc2045ccd9"	=> 1, # ssh_host_rsa_key

	"49d7c55864c1b8880b12229790eca005"	=> 1, # ssh_host_dsa_key
	"15f1c2e98b59fe7671c634322e323e86"	=> 1, # ssh_host_dsa_key.pub
	"cfef65b8e8b6cf7929cc2b6d95b94952"	=> 1, # ssh_host_rsa_key
	"b449907c33ece1e80e40f1e969c0f604"	=> 1, # ssh_host_rsa_key.pub

	# taygete 2018/11/29
	"b763aec38b8d0cdedf6cec3636ea473a"	=> 1, # ssh_host_dsa_key
	"5c97c2e0b8326274413957a39dc89199"	=> 1, # ssh_host_ecdsa_key
	"cf2c90f24e0f9979c51d9415cf5c5612"	=> 1, # ssh_host_ed25519_key
	"2600116708d0db56e5ef25add6738f99"	=> 1, # ssh_host_rsa_key
	"afdea6916c5438dc72c13eab68aa3286"	=> 1, # ssh_host_dsa_key.pub
	"25f6a2d1824930da30177331e44325fd"	=> 1, # ssh_host_ecdsa_key.pub
	"6d88a0177ad1cc130f2ffbda4a751cb4"	=> 1, # ssh_host_ed25519_key.pub
	"fd64ddd64b473f35fc166ddc0a7e25a4"	=> 1, # ssh_host_rsa_key.pub
	# svr-ldap-1
	"17d39111100d108f263f4132031d3adb"	=> 1, # ssh_host_dsa_key
	"f8b8da829f91c42903b7d3ee812b1c18"	=> 1, # ssh_host_ecdsa_key
	"8819505efffeaff2263ea5511231eea4"	=> 1, # ssh_host_ed25519_key
	"8c12429aef6f7072cf7975ad30df762b"	=> 1, # ssh_host_rsa_key
	"78c7c054dac3a27942f022d4d7e200f0"	=> 1, # ssh_host_dsa_key.pub
	"696cd24796b4b4606e43a3981817fc9b"	=> 1, # ssh_host_ecdsa_key.pub
	"8b6bb6567f3b53028293b1f3c7b6d611"	=> 1, # ssh_host_ed25519_key.pub
	"c452098504c7d6b26c242bac321b6c8e"	=> 1, # ssh_host_rsa_key.pub
);

# Machine Class group names which are OK in place of a host name
%cns = (	# "known" generic cn= settings
	"public"	=> 1,
	"sysadmin"	=> 1,
	"pseudousers"	=> 1,
	"pseudoadmin"	=> 1,

#	Dummy entry - this machine doesn't use LDAP (if it tries for user authorisation, it will fail)
	"unused"	=> 1,

#	Legacy - remove in time?
	"fs"		=> 1,
	"security"	=> 1,
	"srg"		=> 1,
);
%ns = %search = %nsrotate = %nstimeout = ();
@ns = ("128.232.1.1", "128.232.1.2", "128.232.1.3");	# our local resolvers.
$ns_local = "127.0.0.53";				# the local machine's resolver ...
$ns_local_inc = 2;					# ... counts as 2 .. or 3?
my $ipmatch = "^(128.232|2001:630:212:2)";
my $ip_len = $ENV{"IP_LEN"} || 20;	# default for Managed Linux VLAN
my $NIC_NAME = $ENV{"NIC_NAME"} || "eth0";
my $ipskip = "^(128.232.0.([67]|71))\$"; # don't ssh to NTP service; dont use krb5 to OTPW ssh service
my $nameskip = "ntp"; # don't ssh to NTP service

# ----------------------------------------------------------------------
# Primary info set by 'readip'
our $firstip = "";
our $firstmac = "";
# Arrays populated by 'readip' which may be of use.
our @devm = ();
our @devs = ();
our @deva = ();
our @devsn = ();
our @devnm = ();
our %addr = ();
$ENV{'PATH'} .= ":/usr/sbin" unless $ENV{PATH} =~ /:\/usr\/sbin:/;
# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# Sample use
# print "Call readip: <", join("|", &readip), ">\n";
# print "Call readip: <", join("|", &readip), ">\n";
# my $res = &readip;
# my ($res1, $res2) = &readip;
# print "firstip = $firstip, firstmac = $firstmac res=$res, res1=$res1, res2=$res2\n";
# print "0=", (&readip)[0], ", 1=", (&readip)[1], "\n";
# for my $n (0 .. $#devs) { printf STDERR "# %2d: %2d: %-10s %15s/%-15s %s\n", $n, $devsn[$n], $devs[$n], $deva[$devsn[$n]] || "", $devnm[$devsn[$n]] || "", $devm[$devsn[$n]] || ""; }
# foreach my $key (sort keys %addr) { printf STDERR "# addr %-10s %s\n", $key, $addr{$key}; };
# exit;
# ----------------------------------------------------------------------


%hosts_srg = %hosts_fs = %hosts_sysadmin = %hosts_public = (); %hosts_self = (); &define_hosts;

for $key (keys %opts) { ${$opts{$key}} = '' if $key =~ /^do_/; }
&read_opts;
GetOptions (%opts) || pod2usage();
my($set) = 0;
for $key (keys %opts) {
	$set = 1 if  $key =~ /^do_/ && (${$opts{$key}} eq "1");
	# print "$set: $key is ${$opts{$key}}\n" if $key =~ /^do_/ && $verbose;
}
pod2usage(-verbose => 2) if $help;

# What should cron, boot and resume do?
# $all = 1 if $cron || $boot || $resume;
my $orig_n = $n;
my $skipped = 0;

$n = $n || $update || $cron || $boot || $resume;
$quietish = $quietish || $cron;

$n = $all = 1 unless $all || $set;
my $cmdpref = ($n) ? "# not run:  " : "";

$host = &shorten_host($host);
$ip = &full_ip($ip);
$issue = "/etc/issue";
my $os = (-e $issue) ? `cat /etc/issue` : `echo -n FreeBSD ''; freebsd-version`;

my $set_user = $user;
my $set_group = $group;

$user = &get_user;
$group = &get_group;

printf STDERR "## host=%s, ip=%s, user=<$user|%s>, group=%s, is_dhcp=%s, is_seckrb5=%s, is_nonfs=%s, uuid=%s\n", $host || "<unset>", $ip || "<unset>", $set_user || "<unset>", $group || "<unset>", &is_dhcp, &is_seckrb5, &is_nonfs, $uuid || "<unset>" if $verbose;

&process_file("$root/etc/hosts", \&process_hosts) if ($all || $do_hosts) && ($do_hosts ne "0");

&process_file("$root/etc/resolv.conf", \&process_resolv) if ($all || $do_resolv) && ($do_resolv ne "0");

&process_file("$root/etc/ldap.conf", \&process_ldap) if ($all || $do_ldapconf) && ($do_ldapconf ne "0");
&process_file("$root/etc/pam_ldap.conf", \&process_ldap) if ($all || $do_ldapconf) && ($do_ldapconf ne "0");

&process_file("$root/etc/udev/rules.d/70-persistent-net.rules", \&process_persistent_net) if ($all || $do_udevnet) && ($do_udevnet ne "0");

&process_file("$root/etc/network/interfaces", \&process_interface) if ($all || $do_network) && ($do_network ne "0") && ! &is_dhcp;
&process_file("$root/etc/sysconfig/network-scripts/ifcfg-eth0", \&process_eth0) if ($all || $do_network) && ($do_network ne "0") && ! &is_dhcp;
system("netplan set network.ethernets.$NIC_NAME.addresses=[$ip/$ip_len]; netplan get") if ($ip) && ($all || $do_network) && ($do_network ne "0") && ! &is_dhcp && -e "/sbin/netplan";

&process_file("$root/etc/hostname", \&process_hostname) if ($all || $do_hostname) && ($do_hostname ne "0") && ! &is_dhcp;
&process_file("$root/etc/sysconfig/network", \&process_network) if ($all || $do_hostname) && ($do_hostname ne "0") && ! &is_dhcp;

&process_file("$root/etc/apt/sources.list", \&process_aptsrc) if ($all || $do_aptsrc) && ($do_aptsrc ne "0");

&process_file("$root/etc/group", \&process_group) if ($set_user || ($all || $do_group) && ($do_group ne "0")) && (&is_seckrb5 || &is_nonfs || $set_user);
&process_file("$root/etc/sudoers", \&process_sudoers) if ($set_group || ($all || $do_sudoers) && ($do_sudoers ne "0")) && (&is_seckrb5 || &is_nonfs || $set_group);

&process_file("$root/etc/apt/sources.list.d/cl.list", \&process_apt) if ($all || $do_aptcl) && ($do_aptcl ne "0");

&check_owner("$root/etc/user-config/bundles", &get_user) if $set_user || ($all || $do_bundles) && ($do_bundles ne "0");
&check_owner("$root/etc/user-config/patches", &get_user) if $set_user || ($all || $do_patches) && ($do_patches ne "0");
&check_group("$root/etc/user-config/bundles", &get_group) if $group || ($all || $do_bundles) && ($do_bundles ne "0");
&check_group("$root/etc/user-config/patches", &get_group) if $group || ($all || $do_patches) && ($do_patches ne "0");
if ($set_user || ($all || $do_hostid) && ($do_hostid ne "0")) {
	unless (-e $hostid)  { $cmd = "touch  $hostid; chmod g+w $hostid; chgrp sysadmin $hostid"; print "${cmdpref}$cmd\n"; system($cmd) unless $n; $skipped++ if $n; }
}
&check_owner($hostid, &get_user) if -e $hostid && ($set_user || ($all || $do_hostid) && ($do_hostid ne "0"));
&check_group($hostid, &get_group) if -e $hostid && ($group || ($all || $do_hostid) && ($do_hostid ne "0"));

&process_krb5_leytab("$root/etc/krb5.keytab", 1) if ($all || $do_keytab) && ($do_keytab ne "0") && &is_seckrb5;

&process_uuid("$root/etc/fstab", "$root/boot/grub/grub.cfg") if (($all || $do_uuid) && ($do_uuid ne "0")) || $uuid || $resetuuid;

&process_sshk("$root/etc/ssh/ssh_host_*{key,pub}") if (($all || $do_sshk) && ($do_sshk ne "0"));

&set_xene_nic if -e "$root/etc/xensource" && ($all || $do_xensource) && ($do_xensource ne "0");

printf STDERR "## host=%s, ip=%s, user=%s, is_dhcp=%s, is_seckrb5=%s, is_nonfs=%s\n", $host || "<unset>", $ip || "<unset>", $user || "<unset>", &is_dhcp, &is_seckrb5, &is_nonfs if $verbose;


if ($skipped) {
	print STDERR "\n\n$0 run in 'no execute mode' - re-run with '-a' to actually make changes\n";
}
# ============================================================================

# process a line of an /etc/sysconfig/network-scripts/ifcfg-eth0 file
sub process_eth0 {
	my ($arg, $val) = @_;

	# null arg is a chance to tidy up;
	if ($arg == $EOF) {
		unless ($seen_ip || $is_dhcp) {
			my($ip) = &ip_host;
			if ($ip) {
				warn("# $0: no 'IPADDR' found in /etc/sysconfig/network-scripts/ifcfg-eth0, so one added\n");
				$val .= "IPADDR=$ip\n";
			}
		} elsif ($seen_ip && $seen_ip > 1) {
			warn("# $0: multiple ($seen_ip) 'IPADDR's found in /etc/sysconfig/network-scripts/ifcfg-eth0\n")
		}
		return $val 
	}

	$is_dhcp = 0 if $val =~ /^BOOTPROTO=static/;
	$is_dhcp = 1 if $val =~ /^BOOTPROTO=dhcp/;
	if ($val =~ /^(IPADDR=)/) {
		my ($pref) = $1;
		my ($ip) = &ip_host;
		$val = "$1$ip\n" if $ip;
		$seen_ip = 0 unless $seen_ip;
		$seen_ip++;
	}
	$val = "#$val" if $val =~ /^HWADDR=/;

	return $val;
}

# ----------------------------------------------------------------------------

# process a line of an /etc/group file
# On Raspberry pi,, look for "<group>:pi*".
# On Ubuntu, look for "<group>:localadmin*".
# On RedHat, look for "wheel:"
sub process_group {
	my ($arg, $val) = @_;
	my ($user) = &get_user;

	return $val if $user eq "root"; # little point.

	# null arg is a chance to tidy up;
	return $val if $arg == $EOF;

	# RedHat
	# return $val =~ s/$/$user/  if $val =~ /^wheel.*:$/;
	# return $val =~ s/$/,$user/ if $val =~ /^wheel/ && $val !~ /$user/;

	# Ubuntu
	return $val if $val =~ s/([:,]localadmin)($|,.*)/$1,$user/;

	# Raspberry pi
	return $val if $val =~ s/([:,]pi)($|,.*)/$1,$user/;

	return $val;
}

# ----------------------------------------------------------------------------

# process a line of an /etc/sudoers file
sub process_sudoers {
	my ($arg, $val) = @_;
	my ($group) = &get_group;

	return $val if $user eq "root"; # little point.

	# null arg is a chance to tidy up;
	return $val if $arg == $EOF;

	$val =~ s/(\s)/,%$group$1/ if $val =~ /^%wheel/ && $val !~ /%$group/;

	return $val;
}

# ----------------------------------------------------------------------------

# process a line of an /etc/apt/sources.list.d/cl.list file
# Look for 
# deb http://www-test.cl.cam.ac.uk/deb CL all # disabled on upgrade to
sub process_apt {
	my ($arg, $val) = @_;

	# null arg is a chance to tidy up;
	return $val if $arg == $EOF;

	$val =~ s/^# (deb http:\/\/.*CL all)\s*#\s*disabled on upgrade to .*/$1/;
	return $val;
}

# ----------------------------------------------------------------------------

# process a line of an /etc/hosts file
sub process_hosts {
	my ($arg, $val) = @_;

	# null arg is a chance to tidy up;
	return $val if $arg == $EOF;

	if ($val =~ /^(127.\S+\s+)(.*\S)/) {
		my($addr,$names) = ($1,$2);

		# Strip off "localhost" preficies and sufficies
		my($suffix) = "";
		while ($names =~ s/(\s+localhost\S*)$//) { $suffix = "$1$suffix"; }
		my($prefix) = "";
		while ($names =~ s/^(localhost\S*\s+)//) { $prefix .= $1; }
		print STDERR "# <$addr> <$prefix> <$names> <$suffix>\n" if $verbose;

		if ($names =~ /^localhost\S*$/) {
			# OK
		#  127.0.0.1 sxp07.ad.cl.cam.ac.uk sxp07 localhost localhost.localdomain
		} elsif ($names =~ /^(\S+)\.ad.cl.cam.ac.uk\s+(\S+)$/) {
			my ($long, $short) = ($1, $2);
			my ($host) = &shortname_host;
			warn("# can't yet process repeated localhost ($host): $_") unless $long eq $host && $short eq $host;
			# OK
		} elsif ($names =~ /localhost/) {
			warn("# can't yet process localhost in middle: $_");
		} elsif ($names =~ /^(localhost.localdomain\s+)(\S+).cl.cam.ac.uk$/) {
			my ($pref) = $1;
			my ($host) = &fullname_host;
			$val = "$addr$pref$host\n" if $host;
		} elsif ($addr =~ /^127.0.1.1/ && $names =~ /^[^. ]+$/) {
			my ($shost) = &shortname_host;
			$val = "$addr$prefix$shost$suffix\n" if $shost;
		} elsif ($names =~ /^[^. \t]*(\s+)\S+\.cl.cam.ac.uk$/) {
			my ($sep) = $1;
			my ($lhost) = &fullname_host;
			my ($shost) = &shortname_host;
			$val = "$addr$prefix$shost$sep$lhost$suffix\n" if $lhost && $shost;
		} elsif ($names =~ /^\S+\.cl.cam.ac.uk(\s+)[^. \t]*$/) {
			my ($sep) = $1;
			my ($lhost) = &fullname_host;
			my ($shost) = &shortname_host;
			$val = "$addr$prefix$lhost$sep$shost$suffix\n" if $lhost && $shost;
		} elsif ($names =~ /\s/) {
			print STDERR "# can't yet process multiple names in '$names': <$prefix> <$names> <$suffix>\n";
		} elsif ($names =~ /\.cl.cam.ac.uk$/) {
			my ($lhost) = &fullname_host;
			$val = "$addr$prefix$lhost$suffix\n" if $lhost;
		} elsif ($names =~ /\./) {
			my ($lhost) = &fullname_host;
			$val = "$addr$prefix$lhost$suffix\n" if $lhost;
		} elsif ($names =~ /\S/) {
			my ($shost) = &shortname_host;
			$val = "$addr$prefix$shost$suffix\n" if $shost;
		} else {
			warn("# Unrecognised: $_");
		}
	}

	return $val;
}

# ----------------------------------------------------------------------------

# process a line of an /etc/sysconfig/network file
sub process_network {
	my ($arg, $val) = @_;

	# null arg is a chance to tidy up;
	if ($arg == $EOF) {
		unless ($seen_network_hostname) {
			my($host) = &fullname_host;
			if ($host) {
				warn("# $0: no 'HOSTNAME' found in /etc/sysconfig/network, so one added\n");
				$val .= "HOSTNAME=$host\n";
			}
		} elsif ($seen_network_hostname > 1) {
			warn("# $0: multiple ($seen_network_hostname) 'HOSTNAME's found in /etc/sysconfig/network\n")
		}
		return $val 
	}

	if ($val =~ /^(HOSTNAME=)/) {
		my ($pref) = $1;
		my ($host) = &fullname_host;
		$val = "$1$host\n" if $host;
		$seen_network_hostname = 0 unless $seen_network_hostname;
		$seen_network_hostname++;
	}

	return $val;
}

# ----------------------------------------------------------------------------

# process a line of an /etc/apt/sources.list.d/uxsup.list file
sub process_aptsrc {
	my ($arg, $val) = @_;

	# null arg is a chance to tidy up;
	return $val if $arg == $EOF;

	# Process pre-processed ones:
	$val =~ s= http://www-uxsup.csx.cam.ac.uk/pub/linux/ubuntu(/*) = $aptsrcu$1 =;
	# Process new ones:
	$val =~ s= http://(\S*)archive.ubuntu.com/ubuntu(/*) = $aptsrcu$2 =;
	$val =~ s= http://security.ubuntu.com/ubuntu = $aptsrcu =;

	return $val;
}

# ----------------------------------------------------------------------------

# process a line of an /etc/hostname file
sub process_hostname {
	my ($arg, $val) = @_;

	# null arg is a chance to tidy up;
	if ($arg == $EOF) {
		unless ($seen_hostname) {
			my($host) = &fullname_host;
			if ($host) {
				warn("# $0: no host name found, so one added\n");
				$val = "$host\n";
			}
		} elsif ($seen_hostname > 1) {
			warn("# $0: multiple ($seen_hostname) host names found\n")
		}
		return $val 
	}

	if ($val =~ /^\S/ && $val !~ /^#/) {
		my($host) = &fullname_host;
		$val = "$host\n" if $host;
		$seen_hostname = 0 unless $seen_hostname;
		$seen_hostname++;
	}

	return $val;
}

# ----------------------------------------------------------------------------

# process a line of an /etc/resolv.conf file
sub process_resolv {
	my ($arg, $val) = @_;
	my ($min_ns) = (&is_dhcp) ? 2 : 3;

	# null arg is a chance to tidy up;
	if ($arg == $EOF) {
		my($s) = 0;
		foreach $key (keys %search) { $s++; };
		$val .= "search	cl.cam.ac.uk\n" unless $s > 0;

		$s = 0;
		foreach $key (keys %nstimeout) { $s++; };
		$val .= "options timeout:1\n" unless $s > 0;

		$s = 0;
		foreach $key (keys %nsrotate) { $s++; };
		$val .= "options rotate\n" unless $s > 0;

		$s = 0;
		foreach $key (keys %ns) { $s++; $s += $ns_local_inc if $key eq $ns_local};
		foreach $ns (@ns) {
			$s++, $val .= "nameserver\t$ns\n" unless $ns{$ns} || $s >= $min_ns;
		}
		return $val 
	}

	$ns{$1}        = 1 if $val =~ m/^\s*nameserver\s+(\S+)/;
	$search{$1}    = 1 if $val =~ m/^\s*search\s+(\S+)/;
	$nstimeout{$1} = 1 if $val =~ m/^\s*options\s+timeout\s*:\s*(\d+)/;
	$nsrotate{"on"}= 1 if $val =~ m/^\s*options\s+rotate/;

	return $val;
}


# ----------------------------------------------------------------------------

# process a line of an ldap.conf file
sub process_ldap {
	my ($arg, $val) = @_;

	# null arg is a chance to tidy up;
	if ($arg == $EOF) {
		unless ($seen_pg) {
			my($host) = &shortname_host;
			if ($host && ! &is_dhcp) {
				warn("# $0: no 'pam_groupdn' in ldap.conf found, so one added\n");
				$val .= "pam_groupdn cn=public,ou=HostGroups,dc=cl,dc=cam,dc=ac,dc=uk\n";
			}
		} elsif ($seen_pg > 1) {
			warn("# $0: multiple ($seen_pg) 'pam_groupdn's found in ldap.conf\n")
		}
		unless ($seen_uri) {
			my($host) = &shortname_host;
			if ($host) {
				warn("# $0: no 'uri' found in ldap.conf, so one added\n");
				$val .= "uri ldap://128.232.0.41/ ldap://128.232.0.42/ ldap://128.232.0.43/\n";
			}
		} elsif ($seen_uri > 1) {
			warn("# $0: multiple ($seen_uri) 'uri's found in ldap.conf\n")
		}
		return $val 
	}

	# pam_groupdn cn=slogin-serv7,ou=HostGroups,dc=cl,dc=cam,dc=ac,dc=uk
	if ($val =~ /^(pam_groupdn\s+cn=)([^,]+)(.*)/) {
		my ($pref, $cn, $oudc) = ($1, $2, $3);
		my ($host) = &shortname_host;
		if ($cns{$cn} && $oudc  =~ /$cloudc/) { ; }
		# Unlikely to be used any more ...
		elsif ($host && $hosts_public{$host}) { $val = "${pref}public$cloudc\n"; }
		elsif ($host && $hosts_sysadmin{$host}) { $val = "${pref}sysadmin$cloudc\n"; }
		elsif ($host && $hosts_fs{$host}) { $val = "${pref}fs$cloudc\n"; }
		elsif ($host && $hosts_srg{$host}) { $val = "${pref}srg$cloudc\n"; }
		elsif ($host && $hosts_self{$host}) { $val = "$pref$host$cloudc\n"; }
		# Drop through ...
		# New default is public
		elsif ($host && ! &is_dhcp) { $val = "${pref}public$cloudc\n"; }
		# NOT USED - old default of the hostname
		elsif ($host && ! &is_dhcp) { $val = "$pref$host$cloudc\n"; }
		$seen_pg = 0 unless $seen_pg;
		$seen_pg++;
	}

	#  uri ldap://128.232.0.41/ ldap://128.232.0.42/ ldap://128.232.0.43
	if ($val =~ m=^uri\s+ldapi:///\s+=) {
		$val = "# Null: $val";
	} elsif ($val =~ /^uri/) {
		$seen_uri = 0 unless $seen_uri;
		$seen_uri++;
	}

	return $val;
}

# ----------------------------------------------------------------------------

# process /etc/krb5.keytab
sub process_krb5_leytab {
	my ($arg, $val) = @_;
	my ($host) = &shortname_host;
	my ($ksrc) =  "$arg-$host";
	my ($oldname) = "";
	my ($warns) = '';
	my (%klist) = ();
	my($thishost, $aliases, $wrong) = (0, 0, 0);
	print STDERR "# Process krb5_leytab ($arg, $val)\n" if $verbose;
	open(KLIST, "(klist -V||klist) 2>&1|") || return; close(KLIST);
	print STDERR "# klist -k $arg\n" if $verbose;
	if (open(KLIST, "klist -k $arg|")) {
	    while (<KLIST>) {
		# Keytab name: FILE:/etc/krb5.keytab
		# KVNO Principal
		# ---- --------------------------------------------------------------------------
		# 4 nfs/ramsey.cl.cam.ac.uk@AD.CL.CAM.AC.UK
		# 9 host/ramsey.cl.cam.ac.uk@AD.CL.CAM.AC.UK
		# 2 host/slogin-serv2.cl.cam.ac.uk@AD.CL.CAM.AC.UK
		next if /^[- ]*$/;
		next if /^KVNO Principal$/;
		next if /^Keytab name: (WR|)FILE:$root\/etc\/krb5.keytab$/;
		if (/^\s*\d+\s+(nfs|host)\/([^@]*)\.cl.cam.ac.uk@/) {
			my($name) = $2;
			$klist{$name} |= 1;
			if (lc $name eq lc $host) {
				$thishost++;
			} elsif (lc $name eq lc "$host$subdomain") {
				$thishost++;
			} elsif ($thishost) {
				print "#K $thishost alias of: $name\n" if $verbose > 1;
				$aliases++;
			} else {
				$warns .= "#K: hostname is $name in $arg\n";
				$oldname = "-$name" unless $oldname;
				$wrong++;
			}
			next;
		}
		print "#k: $_";
	    }
	}
	close(KLIST);

	&readip;
	foreach my $key (sort keys %addr) {
		printf STDERR "# addr %-10s %s\n", $key, $addr{$key} if $verbose;
		my ($addr) = $addr{$key};
		next unless $addr =~ /$ipmatch/;
		next if     $addr =~ /$ipskip/;
		my $hostn = &addr2name($addr);
		next unless $hostn;
		next if     $hostn =~ /$nameskip/;
		$hostn =~ s/.cl.cam.ac.uk$//;
		$klist{$hostn} |= 2;
		print STDERR "# ADDR: $addr -> $hostn ($klist{$hostn})\n" if $verbose;
	}
	my ($names) = 0;
	foreach $name (sort keys %klist) {
		if (($klist{$name} & 3) == 3) {
			$names++;
			print STDERR "# $arg has $name ($names)\n"  if $verbose;
			next;
		}
		if (!($klist{$name} & 1)) {
			printf STDERR "# %-20s missing  from keytab - See http://www.wiki.cl.cam.ac.uk/clwiki/SysInfo/Kerberos#creatingakeytab\n", $name;
		}
		if (!($klist{$name} & 2)) {
			printf STDERR "# %-20s not needed in keytab - See http://www.wiki.cl.cam.ac.uk/clwiki/SysInfo/Kerberos#creatingakeytab\n", $name;
		}
	}

	if ($names == 0) {
		print STDERR "# No valid hostname in $arg\n";
	} elsif ($names  > 1) {
		if (system("grep -q '^GSSAPIStrictAcceptorCheck' /etc/ssh/sshd_config")) {
			print STDERR "# $names: add 'GSSAPIStrictAcceptorCheck no' to /etc/ssh/sshd_config\n";
		}
	}

	if (! $thishost) {
	    print $warns;
	    $ksrc =~ s=$root==                  if $root && ! -e $ksrc;
	    $ksrc = "$arg-\U$host"		if          ! -e $ksrc;
	    $ksrc = "$src/etc/krb5.keytab-\u$host" if       ! -e $ksrc;
	    $ksrc = "$src/etc/krb5.keytab"	if $src  && ! -e $ksrc;
	    $ksrc = "$root/etc/host\u$host"	if $root && ! -e $ksrc;
	    $ksrc = "$src/etc/host\u$host"	if          ! -e $ksrc;
	    if (-e $ksrc) {
		my($mv) = (-e $arg) ? "mv $arg $arg$oldname-$$;" : "";
		my($fix) = "; chown root $arg; chmod 400 $arg";
		print STDERR "${cmdpref}$mv cp -p $ksrc $arg$fix\n";
		system("$mv cp -p $ksrc $arg$fix") unless $n;
		$skipped++ if $n;
	    }
	    else {
	    	my $cmd = "cl-onserver --keytab";
		print STDERR "# sudo $cmd\n";
		$skipped++, return if $n;
		system($cmd);
	    }
	}
}


sub addr2name {
	my ($addr) = @_;
	if ($addr =~ /^128.232/) {
		 return gethostbyaddr(inet_aton($addr), AF_INET);
	}
	if ($addr =~ /^2001:630:212:2/) {
		my $address = inet_pton(AF_INET6, $addr) || return "";
		my $sockaddr = sockaddr_in6($mghba_port, $address) || return "";
		my ($err, $hostname, $servicename) = getnameinfo($sockaddr, $mghba_flags, $mghba_xflags);
		return "" if $err;
		return $hostname;
	}
	return "";
}

# ----------------------------------------------------------------------------

# process a line of an /etc/udev/rules.d/70-persistent-net.rules file
# diff -r -x kera/etc/udev/rules.d/70-persistent-net.rules
# < SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:12:3f:85:90:26", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"
# > SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:12:3f:96:5b:fc", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"
sub process_persistent_net {
	my ($arg, $val) = @_;

	# null arg is a chance to tidy up;
	if ($arg == $EOF) {
		unless ($seen_net) {
			my($mac) = &mac_host;
			if ($mac && $seen_something) {
				warn("# $0: no SUBSYSTEM==\"net\" ... found, so one added\n");
				$val .= "SUBSYSTEM==\"net\", ACTION==\"add\", DRIVERS==\"?*\", ATTR{address}==\"$mac\", ATTR{type}==\"1\", KERNEL==\"eth*\", NAME=\"eth0\"\n";
			}
		} elsif ($seen_net > 1) {
			warn("# $0: multiple ($seen_net) 'eth0's found in -persistent-net\n")
		}
		return $val 
	}

	# SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="70:71:bc:6b:9d:67", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"
	# SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:12:3f:96:5b:fc",                      ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"
	# SUBSYSTEM=="net",                DRIVERS=="?*", ATTR{address}=="00:12:3f:96:5b:fc",                      ATTR{type}=="1", KERNEL=="eth*", NAME="eth0"
	# SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:15:17:24:b9:99",                                                       NAME="eth0" 
	if ($val =~ /^(SUBSYSTEM=="net", DRIVERS=="\?\*", ATTR\{address\}==")(\S+)(", ATTR\{type\}=="1", NAME="eth0"\n)/i ||
		    /^(SUBSYSTEM=="net", DRIVERS=="\?\*", ATTR\{address\}==")(\S+)(", ATTR\{type\}=="1", KERNEL=="eth\*", NAME="eth0"\n)/i ||
		    /^(SUBSYSTEM=="net", ACTION=="add", DRIVERS=="\?\*", ATTR\{address\}==")(\S+)(", NAME="eth0"\n)/i ||
		    /^(SUBSYSTEM=="net", ACTION=="add", DRIVERS=="\?\*", ATTR\{address\}==")(\S+)(", ATTR\{type\}=="1", NAME="eth0"\n)/i ||
		    /^(SUBSYSTEM=="net", ACTION=="add", DRIVERS=="\?\*", ATTR\{address\}==")(\S+)(", ATTR\{type\}=="1", KERNEL=="eth\*", NAME="eth0"\n)/i ||
		    /^(SUBSYSTEM=="net", DRIVERS=="\?\*", ATTR\{address\}==")(\S+)(", NAME="eth0"\n)/i ||
		    /^(SUBSYSTEM=="net", ACTION=="add", DRIVERS=="\?\*", ATTR\{address\}==")(\S+)(", ATTR\{type\}=="1", KERNEL=="eth\*", NAME="eth0"\n)/i ||
		    /^(SUBSYSTEM=="net", ACTION=="add", DRIVERS=="\?\*", ATTR\{address\}==")(\S+)(", ATTR\{dev_id\}=="0x0", ATTR\{type\}=="1", KERNEL=="eth\*", NAME="eth0"\n)/i) {
		my ($pref, $suff) = ($1, $3);
		my ($mac) = lc &mac_host;
		if ($mac) {
			$val = "$pref$mac$suff";
		}
		$seen_net = 0 unless $seen_net;
		$seen_net++;
	}

	# SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:12:3f:96:5b:fc", ATTR{type}=="1", KERNEL=="eth*", NAME="eth1"
	if ($val =~ /^SUBSYSTEM=="net", ACTION=="add", DRIVERS=="\?\*", ATTR\{address\}=="(\S+)", ATTR\{type\}=="1", KERNEL=="eth\*", NAME="eth[1-9]"/) {
		my ($new) = $1;
		my ($mac) = &mac_host;
		$val = "#DUP $val" if $mac && $mac eq $new;
	}
	$seen_something = 1 if $val =~ /^S/;

	return $val;
}

# ----------------------------------------------------------------------------

# process a line of an /etc/network/interfaces file
# diff -r -x kera/etc/network/interfaces
# <       address 128.232.16.52
# >       address 128.232.16.53
sub process_interface {
	my ($arg, $val) = @_;

	# null arg is a chance to tidy up;
	if ($arg == $EOF) {
		unless ($seen_address) {
			my($ip) = &ip_host;
			if ($ip && ! $seen_rightaddress) {
				warn("# $0: no 'address' found in /etc/network/interfaces, so one added\n");
				$val .= "#	address $ip\n";
			}
		} elsif ($seen_address > 1) {
			warn("# $0: multiple ($seen_address) 'address's found in /etc/network/interfaces\n")
		}
		return $val 
	}

	if ($val =~ /^\S/ && $in_eth) {
		unless ($seen_address) {
			my($ip) = &ip_host;
			if ($ip && ! $seen_rightaddress) {
				warn("# $0: no 'address' found in /etc/network/interfaces, so one added\n");
				$val .= "#	address $ip\n";
			}
		}
		$in_eth = 0;
	}
	if ($val =~ /\s*iface\s+eth0\s+inet\s+static/) {
		$in_eth = 1;
	}
	if ($in_eth && $val =~ /(\s+address\s+)(\S+)(.*\n)/) {
		my ($pref, $suff) = ($1, $3);
		my ($ip) = &ip_host;
		if ($ip) {
			$val = "$pref$ip$suff";
		}
		$seen_address = 0 unless $seen_address;
		$seen_address++;
	}
	if (!$seen_rightaddress && $val =~ /address\s+(\S+)/) {
		my($addr) = $1;
		my($ip) = &ip_host;
		$seen_rightaddress = 1 if ! $ip || $addr eq $ip;
	}
	return $val;
}

sub process_sshk() {
	my ($regexp) = @_;
	my (@delete) = ();
	unless (defined $md5sum) {
		$md5sum = "md5sum";
		$md5sum = "md5" if system("type $md5sum > /dev/null 2>&1");
	}
	foreach $file (glob $regexp) {
		my ($sum) = `$md5sum $file`;
		next unless $sum;
		chomp($sum);
		# md5: MD5 ($filename) = 49d7c55864c1b8880b12229790eca005
		$sum =~ s/.*=\s+//;
		# md5sum: 49d7c55864c1b8880b12229790eca005 $file
		$sum =~ s/\s.*//;
		print STDERR "## $file is $sum\n" if $verbose;
		if (defined $sshk_cloned{$sum}) {
			print STDERR "# $file $sum matches cloned value\n";
			push @delete, $file;
		}
	}
	return if $#delete < 0;
	print STDERR "# sudo rm -f @delete; sudo dpkg-reconfigure openssh-server\n";
	$skipped++, return if $n;
	system("rm -f @delete; dpkg-reconfigure openssh-server");
}

sub process_uuid() {
	# "$root/etc/fstab", "$root/boot/grub/grub.cfg", 1)
	my ($fstab, $grub) = @_;
	printf STDERR "# process_uuid($fstab, $grub) uuid=%s%s\n", $uuid || "<unset>", ($resetuuid) ? " RESET UUID" : "" if $verbose;
	unless (-e $fstab && -e $grub ) {
		warn("# Failed to find $fstab and $grub\n") if $os =~ /Ubuntu/i;
		return;
	}
	my $df=`df $root/`;
	chomp($df);
	$df =~ s/^Filesystem [^\n]*\n//m;
	my $dev;
	$dev = $1 if $df =~ m=(/dev/\S+)=;
	unless ($dev) {
		print STDERR "# process_uuid failed to find root device from '$df'\n";
		return 0;
	}
	print STDERR "# process_uuid: root is $dev\n" if $verbose;
	my $tune2fs = `tune2fs -l $dev`;
	my $fsuuid = $1 if $tune2fs =~ /Filesystem UUID:\s*(\S+)/m;
	unless ($fsuuid) {
		print STDERR "# process_uuid failed to find UUID for $dev '$tune2fs'\n";
		return 0;
	}
	my $newuuid = $uuid || $fsuuid;
	print STDERR "# process_uuid: fsuuid is $fsuuid newuuid is $newuuid\n" if $verbose;
	if ($clone_uuid{$fsuuid}) {
		print STDERR "# process_uuid: UUID $fsuuid is a clone\n";
		$resetuuid = 1 unless $n || $uuid;
	}
	$newuuid = &newuuid if $resetuuid;
	# FORCE FAULT: $fsuuid =~ s/-/=/; print STDERR "# fsuuid is $fsuuid\n" if $verbose;
	my ($fstab_OK, $fstab_changed) = &check_fstab($fstab, $fsuuid, $newuuid, $do_uuid || $resetuuid || $uuid, 0);
	my ($search_OK, $linux_OK, $grub_changed) = &check_grub($grub, $fsuuid, $newuuid, $do_uuid || $resetuuid || $uuid, 0);
	unless ($fstab_OK > 0 && $search_OK > 0 && $linux_OK > 0) {
		print STDERR "# process_uuid checks failed: $fstab_OK $search_OK $linux_OK\n";
		return if ! $fstab_OK || ! $search_OK || ! $linux_OK;
	}
	return unless $fstab_changed || $grub_changed;
	print STDERR "# UUID changed $fstab_changed || $grub_changed\n";
	$skipped++, return if $n;

	my $cmd = "tune2fs -U $newuuid $dev";
	if (($newuuid ne $fsuuid) && system($cmd)) {
		print STDERR "# '$cmd' failed\n";
		return;
	}
	&check_fstab($fstab, $fsuuid, $newuuid, $do_uuid || $resetuuid || $uuid, 1);
	&check_grub($grub, $fsuuid, $newuuid, $do_uuid || $resetuuid || $uuid, 1);
}

sub newuuid {
	my $format = "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x";
	return sprintf $format,  split(/\./, &ip_host || "1.2.3.4"), &randarray(6), 0x00, 0x00, split(/\./, &ip_host || "4.3.2.1");
}

sub randarray {
	my ($elem) = @_;
	my @res;
	while ($elem-- > 0) { push @res, int rand(256); }
	return @res;
}
# ============================================================================

# check that the uuid in fstab is as expected and/or change it
sub check_fstab() {
	my ($filename, $oldu, $newu, $force, $write) = @_;
	print STDERR "# check_fstab($filename, $oldu, $newu)\n" if $verbose;
	open(IN, $filename) || return warn("$0: failed to open '$filename': $!\n");
	my $res = '';
	my $raw = '';
	my $seen = 0;
	while (<IN>) {
		$raw .= $_;
		if (/^([^#]\S*)\s+\/\s/) {
			my $dev = $1;
			print STDERR "# root: <$dev>: $_" if $verbose;
			if ($dev =~ /^UUID=$oldu/i) {
				$seen = 1;
				print STDERR "# UUID in $filename is $oldu: $dev\n" if $verbose;
				s/^UUID=\S+/UUID=$newu/i;
			} elsif ($dev =~ /^UUID=/i) {
				$seen = -1;
				print STDERR "# UUID in $filename not $oldu but $dev\n";
				s/^UUID=\S+/UUID=$newu/i;
			} else {
				$seen = -2;
				print STDERR "# No UUID in $filename: $dev\n";
				s/^\S+/UUID=$newu/i;
			}
		}
		$res .= $_;
	}
	if ($res ne $raw) {
		print STDERR "# $filename changed for $oldu to $newu ($write)\n";
		&replace_file($filename, $res) if $write;
	}
	return ($seen, $res ne $raw);
}

# ============================================================================

# process a file, and replace if needed
# search --no-floppy --fs-uuid --set ea5e96eb-f9a7-4c09-affb-fe523e524a7d
# search --no-floppy --fs-uuid --set=root ca178818-2f70-4e46-93f4-be1605deb653
# linux  /boot/vmlinuz-3.2 root=UUID=ca178818-2f70-4e46-93f4-be1605deb653 ro

sub check_grub() {
	my ($filename, $oldu, $newu, $force, $write) = @_;
	print STDERR "# check_grub($filename, $oldu, $newu)\n" if $verbose;
	open(IN, $filename) || return warn("$0: failed to open '$filename': $!\n");
	my $res = '';
	my $raw = '';
	my $seen = 0;
	my $linux = 0;
	while (<IN>) {
		$raw .= $_;
		if (/^\s*search\s.*\s([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/ || /\s--set=root\s+(\S+)/ || /^\s*search\s.*\s--set\s+(\S+)/) {
			my $uuid = $1;
			print STDERR "# set <$uuid>: $_" if $verbose > 1;
			if ($uuid =~ /^$oldu$/i) {
				$seen = 0 if $seen < 0;
				$seen++;
				print STDERR "# search UUID in $filename is $oldu: $uuid\n" if $verbose;
				s/$uuid/$newu/i;
			} else {
				$seen -= 1 unless $seen > 0;
				print STDERR "# wrong search UUID in $filename: $uuid rather than $oldu\n" unless $seen > 0;
				s/$uuid/$newu/i if $seen < 0;
			}
		}
		if (/\sroot=UUID=(\S+)/) {
			my $uuid = $1;
			print STDERR "# set <$uuid>: $_" if $verbose > 1;
			if ($uuid =~ /^$oldu$/i) {
				$linux = 0 if $linux < 0;
				$linux++;
				print STDERR "# root UUID in $filename is $oldu: $uuid\n" if $verbose;
				s/$uuid/$newu/i;
			} else {
				$linux -= 1 unless $linux > 0;
				print STDERR "# wrong root UUID in $filename: $uuid rather than $oldu\n" unless $linux > 0;
				s/$uuid/$newu/i if $linux < 0;
			}
		}
		$res .= $_;
	}
	if ($res ne $raw) {
		print STDERR "# $filename changed for $oldu to $newu ($write)\n";
		&replace_file($filename, $res) if $write;
	}
	return ($seen, $linux, $res ne $raw);
}

# ============================================================================

# process a file, and replace if needed
sub process_file() {
	my ($filename, $process) = @_;
	print STDERR "# process_file($filename, $process)\n" if $verbose;
	return unless -e $filename;
	open(IN, $filename) || return warn("$0: failed to open '$filename': $!\n");
	my($res) = '';
	my($src) = '';
	while (<IN>) {
		$src .= $_;
		$res .= &$process($DATA, $_);
	}
	close(IN);
	$res = &$process($EOF, $res);
	if ($res eq $src) {
		print STDERR "# $0: $filename unchanged\n" if $verbose;
		return;
	}
	&replace_file($filename, $res);
}

# ============================================================================

sub replace_file() {
	my($filename, $res) = @_;

	print STDERR "# $0: $filename changed\n";
	# return if $n;
	$temp = "$filename-t";
	unlink $temp;
	unless (open(OUT, ">$temp")) {
		warn("$0: open $temp failed: $!\n");
		return;
	}
	unless (print OUT $res) {
		warn("$0: write $temp failed: $!\n");
		return;
	}
	unless (close(OUT)) {
		warn("$0: close $temp failed: $!\n");
		return;
	}
	if ($verbose || ! $verbose) {
		system("diff $filename $temp | head -30 | cat -v");
	}
	unlink $temp, $skipped++, return if $n;
	@stat = stat($filename);
	chmod $stat[2], $temp if $stat[2];
	unless (rename $temp, $filename) {
		warn("$0: rename $temp to $filename failed: $!\n");
		return;
	}
	system("type -p restorecon > /dev/null && restorecon -v $filename");
}

# ============================================================================

# Convert a short form IP address with optional length to a full form
sub full_ip {
	my($ip) = @_;
	return $ip unless $ip;
	chomp($ip);
	$ip_len = $1 if $ip =~ s=/(\d+)$==;
	return $ip if $ip =~ /^\d+\.\d+\.\d+\.\d+$/;
	return "128.$ip" if $ip =~ /^\d+\.\d+\.\d+$/;
	return "128.232.$ip" if $ip =~ /^\d+\.\d+$/;
	return "128.232.0.$ip" if $ip =~ /^\d+$/;
	warn("$0: unexpected IP address '$ip'\n");
	return $ip;
}

# Convert a FQDN to a [PU]QDN
sub shorten_host {
	my($host) = @_;
	return $host unless $host;
	chomp($host);
	$subdomain = "";
	$subdomain = $1 if $host =~ /^[^\.]*(\..*)\.cl.cam.ac.uk$/;
	print STDERR "## subdomain is '$subdomain'\n" if $verbose;
	$host =~ s/\..*//;
	return $host;
}

# Return the short name of this host
sub shortname_host {
	return $host if $host;
	if ($changed) {
		# Get latest IP address and map it to a name
		my ($ip) = &ip_host;
		if ($ip) {
			print STDERR "# Change: IP is $ip\n" if $verbose;
			my ($txt) = `host $ip`;
			print STDERR "# Change: Host is <<$txt>>\n" if $verbose;
			if ($txt =~ /pointer (\S+)/) {
				print STDERR "# Change: Host is $1\n" if $verbose;
				$host = &shorten_host($1);
				print STDERR "# \$host is now $host\n" if $verbose;
				return $host if $host;
			}
		}
	}
	$host = &shorten_host(`uname -n`);
	print STDERR "# \$host is now $host\n" if $verbose;
	return $host if $host;
	return $host;
}

# Return the full name of this host
sub fullname_host {
	return $fullhost if $fullhost;
	my($val) = &shortname_host;
	return $fullhost unless $val;
	chomp($val);
	$val =~ s/$/$subdomain.cl.cam.ac.uk/ unless $val =~ /\./;
	$fullhost = $val;
	print STDERR "# \$fullhost is now $fullhost\n" if $verbose;
	return $fullhost;
}

# ----------------------------------------------------------------------------

# Check the owner of a file
sub check_owner {
	my($file, $user) = @_;
	
	return unless -e $file;

	return unless $line = `ls -ld $file`;

	print STDERR "# test line for check_owner is: $line" if $verbose;

	return if $line =~ /^\S+\s+\d+\s+$user\s/;

	print "${cmdpref}chown $user $file\n";

	$skipped++, return if $n;

	system("chown $user $file");
}

# ----------------------------------------------------------------------------

# Check the group of a file
sub check_group {
	my($file, $user) = @_;
	
	return unless -e $file;

	return unless $line = `ls -ld $file`;

	print STDERR "# test line for check_group  is: $line" if $verbose;

	return if $line =~ /^\S+\s+\d+\s+\S+\s+$user\s/;

	print "${cmdpref}chgrp $user $file\n";

	$skipped++, return if $n;

	system("chgrp $user $file");
}

# ----------------------------------------------------------------------------

# Return the mac address of this host
sub mac_host {
	return $mac if $mac;
	my ($ip, $mac) = &readip;
	$ip if 0; # keep -w happy :-(
	return $mac;
}

# ----------------------------------------------------------------------------

# Return the user
sub get_user {
	return $user if $user; # || ! $root;
	return $Buser if $Buser;

	return unless $line = `ls -ld $src/etc/user-config/bundles`;

	print STDERR "# test line for get_user  is: $line" if $verbose;

	$Buser = $1 if $line =~ /^\S+\s+\d+\s+(\S+)\s/;

	return $Buser;
}

# ----------------------------------------------------------------------------

# Return the group
sub get_group {
	return $group if $group; # || ! $root;
	return $Bgroup if $Bgroup;

	return unless $line = `ls -ld $src/etc/user-config/bundles`;

	print STDERR "# test line for get_group is: $line" if $verbose;

	$Bgroup = $1 if $line =~ /^\S+\s+\d+\s+\S+\s+(\S+)\s/;

	return $Bgroup;
}

# ----------------------------------------------------------------------------

# Return the ip address of this host
sub ip_host {
	return $ip if $ip;
	if ($host && $changed && (($junk, $junk, $junk, $junk, $packed) = gethostbyname($host))) {
		$ip = join('.', unpack('C4',$packed));
	} else {
		my ($ip, $mac) = &readip;
	        $mac if 0; # keep -w happy :-(
	}
	print STDERR "# \$ip is now $ip\n" if $verbose;
	return $ip;
}

# ----------------------------------------------------------------------------

# ----------------------------------------------------------------------------

sub set_xene_nic {
    $management = 0;	# manage using eth0
    $ip = $ENV{'IP'};
    $dns="128.232.13.20,128.232.13.18";
    %seen = %reset = %uuid = %mac = ();
    
    chomp($HOSTNAME=`uname -n`);
    $HOSTNAME =~ s/\.cl.cam.ac.uk$// || $HOSTNAME =~ s/\..*//;
    die("$0: failed to find hostname\n") unless $HOSTNAME;
    print STDERR "HOSTNAME is $HOSTNAME\n" if $trace;
    
    return unless `type xe 2>&1`;
    $HUUID=	`sudo xe host-list name-label=$HOSTNAME.cl.cam.ac.uk --minimal` ||
	`sudo xe host-list name-label=$HOSTNAME --minimal` ||
    chomp($HUUID);
    chomp($HUUID);
    die("$0: failed to find HUUID\n") unless $HUUID;
    print "HOSTNAME is '$HOSTNAME', HUUID is '$HUUID'\n" if $trace;
    
    # Find existing mappings
    $cmd = "sudo xe pif-list host-uuid=$HUUID VLAN=-1 params=MAC,device,uuid|";
    open(IN, $cmd) || die("$0: open on '$cmd': $!\n");
    print STDERR "Use: $cmd\n" if $trace;
    while (<IN>) {
	# uuid ( RO)      : c2e62d03-5dab-0ef5-4ed6-ab39b7d22d81
	# device ( RO)    : eth0
	# MAC ( RO): 00:1e:68:57:89:4c
	if (/device.*:\s*$eth(\d+)/) { print STDERR "dev is $1: $_" if $trace; $dev = $1; $uuid{$dev} = $uuid; }
	elsif (/uuid\s.*:\s+(\S+)/) { print STDERR "uuid is $1\n" if $trace; $uuid = $1 }
	elsif (/MAC.*:\s+(\S+)/) { print STDERR "MAC is $1\n" if $trace; $mac{$dev} = lc $1 }
	elsif ($trace && $_ ne "\n") { print }
    }
    foreach $key (sort keys %mac) { print "$eth$key is $mac{$key}\n" if $trace }
    
    # Find existing MAC addresses
    $cmd = "ifconfig|";
    # eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    #         inet 128.232.64.10  netmask 255.255.240.0  broadcast 128.232.79.255
    #         inet6 fe80::219:d1ff:fe8a:6659  prefixlen 64  scopeid 0x20<link>
    #         inet6 2001:630:212:238:219:d1ff:fe8a:6659  prefixlen 64  scopeid 0x0<global>
    #         ether 00:19:d1:8a:66:59  txqueuelen 1000  (Ethernet)
    # 
    # 
    # bond0     Link encap:Ethernet  HWaddr 00:30:48:fe:c3:14  
    #           inet addr:128.232.64.213  Bcast:128.232.79.255  Mask:255.255.240.0
    #           inet6 addr: 2001:630:212:200::123:1d/128 Scope:Global
    #           inet6 addr: 2001:630:212:200::22:99/128 Scope:Global
    #           inet6 addr: 2001:630:212:238:230:48ff:fefe:c314/64 Scope:Global
    #           inet6 addr: 2001:630:212:200::22:0/128 Scope:Global
    #           inet6 addr: 2001:630:212:200::22:2/128 Scope:Global
    #           inet6 addr: fe80::230:48ff:fefe:c314/64 Scope:Link
    open(IN, $cmd) || die("$0: open on '$cmd': $!\n");
    print STDERR "Use: $cmd\n" if $trace;
    while (<IN>) {
	# eth0      Link encap:Ethernet  HWaddr 00:1E:68:57:89:4C  
	#           ether 00:19:d1:8a:66:59  txqueuelen 1000  (Ethernet)
	print STDERR "##1 $_" if $trace;
	$ip = $1, next if /inet addr:\s*(128.232.\S+)/;
	$ip = $1, next if /inet \s*(128.232.\S+)/;
	if (/^([a-z]+)(\d+)/) {
		$ethtype = $1;
		$dev = $2;
		next if /flags=/;	# That's all if 'ip' type output.
		next unless /$eth(\d+)\s.*HWaddr\s+(\S+)/;
	} else { # 'ip' type output ?
		next unless /(ether)\s+(\S+)\s/;
		print STDERR "# $ethtype vs $eth for $2: $_";
		next unless $ethtype eq $eth;
	}
	$mac = lc $2;
	print STDERR "dev is $dev, mac is $mac\n" if $trace;
	$seen{$dev} = 1, next if $mac{$dev} && $mac{$dev} eq $mac;
	print "sudo xe pif-forget uuid=$uuid{$dev} # $eth$dev $mac{$dev}\n" if $uuid{$dev};
	print "sudo xe pif-introduce host-uuid=$HUUID device=$eth$dev mac=$mac\n# $eth$dev was $mac{$dev}\n" if $uuid{$dev};
	$reset{$dev} = 1;
    }
    
    # Forget non available interfaces
    foreach $dev (sort keys %mac) {
	print "sudo xe pif-forget uuid=$uuid{$dev} # $eth$dev $mac{$dev}\n" unless $seen{$dev} || $seen{$dev};
    }
    
    # Setup management if it has changed
    if ($reset{$management}) {
	die("$0: no IP address\n") unless $ip;
	chomp($pif=`sudo xe pif-list host-uuid=$HUUID VLAN=-1 device=$eth$management --minimal`);
	print "sudo xe pif-reconfigure-ip uuid=$pif mode=static IP=$ip netmask=255.255.240.0 DNS=$dns\n";
	print "sudo xe host-management-reconfigure pif-uuid=$pif\n";
    }
}

sub read_opts {
	my($file) = $hostid;
	return unless open(OPTS, $file);
	while (<OPTS>) {
		next if /^\s*#/;
		next if /^\s*$/;
		if (/^\s*(\S+)\s*=\s*(\S+)\s*$/) {
			my($key) = "do_$1";
			my($val) = $2;
			$key =~ s/^do_do_/do_/;
			if (defined($$key)) {
				# print STDERR "# $key was '$$key'\n";
				$$key = $val;
				# print STDERR "# $key now '$$key'\n";
			} else { print STDERR "## Unknown variable $key in $file\n" }
		} else { print STDERR "## duff line in $file: $_" }
	}
	close(OPTS);
}

sub is_nonfs {
	return $is_nonfs if defined $is_nonfs;
	$is_nonfs = ! -e "/etc/auto.master";
	return $is_nonfs;
}

sub is_seckrb5 {
	return $is_seckrb5 if defined $is_seckrb5;

	if (open(MASTER, "/etc/default/autofs")) {
		while (<MASTER>) {
			$is_seckrb5 = 1 if /^OPTIONS=" -O sec=krb5"/;
			$is_seckrb5 = 1 if /^daemonoptions="\$daemonoptions sec=krb5"/;
		}
		close(MASTER);
	}
	return $is_seckrb5 if defined $is_seckrb5;

	if (open(MASTER, "/etc/auto.master")) {
		while (<MASTER>) {
			$is_seckrb5 = 0 if /^\+auto\.master/;
			$is_seckrb5 = 1 if /^\+auto\_master/;
		}
		close(MASTER);
	}
	return $is_seckrb5 if defined $is_seckrb5;

	$is_seckrb5 = 0;
	return $is_seckrb5;
}

sub is_dhcp  {
	return $is_dhcp if defined $is_dhcp;
	if (open(MASTER, "/etc/sysconfig/network-scripts/ifcfg-eth0")) {
		while (<MASTER>) {
			$is_dhcp = 1 if /^BOOTPROTO=dhcp/;
			$is_dhcp = 0 if /^BOOTPROTO=static/;
		}
		close(MASTER);
		print STDERR "# is_dhcp is $is_dhcp after ifcfg-eth0\n" if $verbose;
	}
	return $is_dhcp if defined $is_dhcp;

	if (open(MASTER, "/etc/network/interfaces")) {
		while (<MASTER>) {
			$is_dhcp = 0 if /^\s*iface\s+eth0\s+inet\s+static/;
			$is_dhcp = 1 if /^\s*iface\s+eth0\s+inet\s+d/;
		}
		close(MASTER);
		print STDERR "# is_dhcp is $is_dhcp for eth0 after interfaces\n" if $verbose;
	}
	return $is_dhcp if defined $is_dhcp;

	$is_dhcp = 0;
	return $is_dhcp;
}


# mkdir /tmp/h
# for h in $(~/tmp/cl-hosts DIST_linux); do ldapsearch -x -b ou=HostGroups,dc=cl,dc=cam,dc=ac,dc=uk cn=$h | grep uniqueMember: | wc -l > /tmp/h/$h; done 
# grep -l  ^474$ * > /tmp/hosts
# for a in $(cat /tmp/H); do grep for.public $a/proc/ldap-stat && echo $a; done


sub define_hosts {
	%hosts_fs = (
	);
	%hosts_srg = (
	);
	%hosts_sysadmin = (
	);
	%hosts_public = (
	);
	%hosts_self = (
	);
}





# ----------------------------------------------------------------------
# 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
#     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
#     inet 127.0.0.1/8 scope host lo
#         valid_lft forever preferred_lft forever
#     inet6 ::1/128 scope host
#         valid_lft forever preferred_lft forever
# 2: eth0: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 1000
#     link/ether 00:30:48:fe:c3:14 brd ff:ff:ff:ff:ff:ff
# 3: eth1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP group default qlen 1000
#     link/ether 00:30:48:fe:c3:14 brd ff:ff:ff:ff:ff:ff
# 4: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
#     link/ether 00:30:48:fe:c3:14 brd ff:ff:ff:ff:ff:ff
#     inet 128.232.64.213/20 brd 128.232.79.255 scope global bond0
#         valid_lft forever preferred_lft forever
#     inet 128.232.0.6/32 brd 128.232.0.6 scope global bond0:6
#         valid_lft forever preferred_lft forever
#     inet6 2001:630:212:200::22:0/128 scope global deprecated 
#        valid_lft forever preferred_lft 0sec
#     inet6 2001:630:212:238:230:48ff:fefe:c314/64 scope global mngtmpaddr dynamic 
#        valid_lft 2591919sec preferred_lft 604719sec
#     inet6 fe80::230:48ff:fefe:c314/64 scope link 
#        valid_lft forever preferred_lft forever
# 5: lxcbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
#     link/ether 00:16:3e:00:00:00 brd ff:ff:ff:ff:ff:ff
#     inet 10.0.3.1/24 scope global lxcbr0
#        valid_lft forever preferred_lft forever
# 6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
#     link/ether 02:42:40:5a:42:05 brd ff:ff:ff:ff:ff:ff
#     inet 192.168.8.182/24 scope global docker0
#        valid_lft forever preferred_lft forever
# 7: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
#     link/ether 52:54:00:5e:9b:c3 brd ff:ff:ff:ff:ff:ff
#     inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
#        valid_lft forever preferred_lft forever
# 8: virbr0-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master virbr0 state DOWN group default qlen 1000
#     link/ether 52:54:00:5e:9b:c3 brd ff:ff:ff:ff:ff:ff



# bond0     Link encap:Ethernet  HWaddr 00:30:48:fe:c3:14  
#           inet addr:128.232.64.213  Bcast:128.232.79.255  Mask:255.255.240.0
#           inet6 addr: 2001:630:212:200::123:1d/128 Scope:Global
#           inet6 addr: 2001:630:212:238:230:48ff:fefe:c314/64 Scope:Global
#           inet6 addr: fe80::230:48ff:fefe:c314/64 Scope:Link
#           UP BROADCAST RUNNING MASTER MULTICAST  MTU:1500  Metric:1
#           RX packets:351381748 errors:0 dropped:8 overruns:0 frame:0
#           TX packets:331970558 errors:0 dropped:4 overruns:0 carrier:0
#           collisions:0 txqueuelen:1000 
#           RX bytes:231353491453 (231.3 GB)  TX bytes:194123264893 (194.1 GB)
# 
# bond0:6   Link encap:Ethernet  HWaddr 00:30:48:fe:c3:14  
#           inet addr:128.232.0.6  Bcast:128.232.0.6  Mask:255.255.255.255
#           UP BROADCAST RUNNING MASTER MULTICAST  MTU:1500  Metric:1
# 
# docker0   Link encap:Ethernet  HWaddr 02:42:40:5a:42:05  
#           inet addr:192.168.8.182  Bcast:0.0.0.0  Mask:255.255.255.0
#           UP BROADCAST MULTICAST  MTU:1500  Metric:1
#           RX packets:0 errors:0 dropped:0 overruns:0 frame:0
#           TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
#           collisions:0 txqueuelen:0 
#           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
# 
# eth0      Link encap:Ethernet  HWaddr 00:30:48:fe:c3:14  
#           UP BROADCAST RUNNING SLAVE MULTICAST  MTU:1500  Metric:1
#           RX packets:53696114 errors:0 dropped:3 overruns:0 frame:0
#           TX packets:320838416 errors:0 dropped:0 overruns:0 carrier:0
#           collisions:0 txqueuelen:1000 
#           RX bytes:16926405846 (16.9 GB)  TX bytes:191740020657 (191.7 GB)
#           Memory:f0000000-f001ffff 
# 
# eth1      Link encap:Ethernet  HWaddr 00:30:48:fe:c3:14  
#           UP BROADCAST RUNNING SLAVE MULTICAST  MTU:1500  Metric:1
#           RX packets:297685634 errors:0 dropped:5 overruns:0 frame:0
#           TX packets:11132142 errors:0 dropped:0 overruns:0 carrier:0
#           collisions:0 txqueuelen:1000 
#           RX bytes:214427085607 (214.4 GB)  TX bytes:2383244236 (2.3 GB)
#           Memory:f0060000-f007ffff 
# 
# lo        Link encap:Local Loopback  
#           inet addr:127.0.0.1  Mask:255.0.0.0
#           inet6 addr: ::1/128 Scope:Host
#           UP LOOPBACK RUNNING  MTU:65536  Metric:1
#           RX packets:14939269 errors:0 dropped:0 overruns:0 frame:0
#           TX packets:14939269 errors:0 dropped:0 overruns:0 carrier:0
#           collisions:0 txqueuelen:1 
#           RX bytes:25712039990 (25.7 GB)  TX bytes:25712039990 (25.7 GB)
# 
# lxcbr0    Link encap:Ethernet  HWaddr 00:16:3e:00:00:00  
#           inet addr:10.0.3.1  Bcast:0.0.0.0  Mask:255.255.255.0
#           UP BROADCAST MULTICAST  MTU:1500  Metric:1
#           RX packets:0 errors:0 dropped:0 overruns:0 frame:0
#           TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
#           collisions:0 txqueuelen:1000 
#           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
# 
# virbr0    Link encap:Ethernet  HWaddr 52:54:00:5e:9b:c3  
#           inet addr:192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0
#           UP BROADCAST MULTICAST  MTU:1500  Metric:1
#           RX packets:0 errors:0 dropped:0 overruns:0 frame:0
#           TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
#           collisions:0 txqueuelen:1000 
#           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
# 
# virbr0-nic Link encap:Ethernet  HWaddr 52:54:00:5e:9b:c3  
#           BROADCAST MULTICAST  MTU:1500  Metric:1
#           RX packets:0 errors:0 dropped:0 overruns:0 frame:0
#           TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
#           collisions:0 txqueuelen:1000 
#           RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
# 

sub readip {
    return ($firstip, $firstmac) if $firstip;
    my $devn = -1;
    my $devX = '';
    my $devt = '';
    my $devno = 1;	# for 'ifconfig': device number starting at 1 as per 'ip address'.
    my @ipcmds = ("ip address show", "ifconfig");

    # Try commands until one seems to work
    foreach my $cmd (@ipcmds) {
	if ($#devs < 0) {
	    if(open(IP, "$cmd 2>&1|")) {
		# print STDERR "# <$cmd> \$#devs = $#devs\n";
		while (<IP>) {
			if (/^(\d+):\s+(\S+): </ || /([\0]*)(\S+)\s+Link encap/) {
				my ($Tdevn, $TdevX) = ($1, $2);
				$Tdevn = $devno++ unless $Tdevn; # HACK it for 'ifconfig'.
				&finish_dev($devn, $devX, $devt); # Process the now completed iterface
				($devn, $devX, $devt) = ($Tdevn, $TdevX, '');
			}
			$devt .= $_;
		}
    		&finish_dev($devn, $devX, $devt); # Process the now completed final iterface
		# If anything useful found, export it
		if ($#devs >= 0) {
			$firstip = $deva[$devsn[0]];
			$firstmac = $devm[$devsn[0]];
		}
	    } else { print STDERR "# <$cmd> failed: $? : $! (\$#devs = $#devs)\n" }
	}

    }
    # for my $n (0 .. $#devs) { printf STDERR "# %2d: %2d: %-10s %15s/%-15s %s\n", $n, $devsn[$n], $devs[$n], $deva[$devsn[$n]] || "", $devnm[$devsn[$n]] || "", $devm[$devsn[$n]] || ""; }
    return ($firstip, $firstmac);
}

sub finish_dev {
	my ($devn, $devX, $devt) = (@_);
	if ($devn >= 0) {
#     inet 128.232.0.6/32 brd 128.232.0.6 scope global bond0:6
#     inet6 2001:630:212:200::22:0/128 scope global deprecated 
		# print STDERR "## $devn $devX = <<$devt>>\n";

		# Find IP (4 annd 6) addresses e.g. to check keytab named
		my @addrs =  ($devt =~ m/inet[6]?\s+([0-9a-fA-F.:]+)\//g);
		# print STDERR "### <<$devt>> gave $#addrs: ", join(",", @addrs), "\n";
		foreach my $n (0 .. $#addrs) { $addr{"$devX--" . sprintf("%02d", $n)} = $addrs[$n]; };

		# Find MAC addresses
		if ($devt =~ /\n\s*link\/ether\s+([0-9a-fA-F:]+)/m || $devt =~ /HWaddr\s+([0-9a-fA-F:]+)/m) {
			$devm[$devn] = $1;
			# print STDERR "## $devn $devX = $1\n";
		}

		# Find primary IPv4 address and net len/mask
		if ($devt =~ /inet ([0-9.]*)\/(\d+).*scope global $devX\n/m || $devt =~ /inet addr:([0-9.]*).*Mask:([0-9.]+)/m || $devt =~ /inet addr:([0-9.]*)(\n)*/m) {
			push @devs, $devX;
			push @devsn, $devn;
			$deva[$devn] = $1;
			$devnm[$devn] = $2;
		}
	}
}
# ----------------------------------------------------------------------
