2010-01-12 20:15:11 +00:00
|
|
|
package App::ClusterSSH::Host;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
use version;
|
2010-09-09 12:17:43 +01:00
|
|
|
our $VERSION = version->new('0.03');
|
2010-01-12 20:15:11 +00:00
|
|
|
|
|
|
|
use Carp;
|
2014-07-02 22:39:36 +01:00
|
|
|
use Net::hostent;
|
2010-01-12 20:15:11 +00:00
|
|
|
|
|
|
|
use base qw/ App::ClusterSSH::Base /;
|
|
|
|
|
2010-01-28 19:10:31 +00:00
|
|
|
our %ssh_hostname_for;
|
|
|
|
our %ssh_configs_read;
|
2010-01-12 20:15:11 +00:00
|
|
|
|
|
|
|
sub new {
|
|
|
|
my ( $class, %args ) = @_;
|
|
|
|
|
2011-07-01 16:40:29 +01:00
|
|
|
if ( !$args{hostname} ) {
|
|
|
|
croak(
|
|
|
|
App::ClusterSSH::Exception->throw(
|
|
|
|
error => $class->loc('hostname is undefined')
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2010-01-12 20:15:11 +00:00
|
|
|
|
|
|
|
# remove any keys undef values - must be a better way...
|
2014-02-01 13:47:42 +00:00
|
|
|
foreach my $remove (qw/ port username geometry /) {
|
2010-01-12 20:15:11 +00:00
|
|
|
if ( !$args{$remove} && grep {/^$remove$/} keys(%args) ) {
|
|
|
|
delete( $args{$remove} );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-07-01 16:40:29 +01:00
|
|
|
my $self
|
|
|
|
= $class->SUPER::new( ssh_config => "$ENV{HOME}/.ssh/config", %args );
|
2010-01-28 19:10:31 +00:00
|
|
|
|
|
|
|
# load in ssh hostname for later use
|
2010-01-29 10:23:08 +00:00
|
|
|
if ( !%ssh_hostname_for || !$ssh_configs_read{ $self->{ssh_config} } ) {
|
|
|
|
$ssh_configs_read{ $self->{ssh_config} } = 1;
|
|
|
|
if ( open( my $ssh_config_fh, '<', $self->{ssh_config} ) ) {
|
2010-09-09 12:17:43 +01:00
|
|
|
while ( my $line = <$ssh_config_fh> ) {
|
|
|
|
chomp $line;
|
2011-07-01 16:40:29 +01:00
|
|
|
next unless ( $line =~ m/^\s*host\s+(.*)/i );
|
2010-01-29 10:23:08 +00:00
|
|
|
|
2010-01-28 19:10:31 +00:00
|
|
|
# account for multiple declarations of hosts
|
|
|
|
$ssh_hostname_for{$_} = 1 foreach ( split( /\s+/, $1 ) );
|
|
|
|
}
|
|
|
|
close($ssh_config_fh);
|
|
|
|
|
2010-01-29 10:23:08 +00:00
|
|
|
$self->debug( 5, 'Have the following ssh hostnames' );
|
2011-07-01 16:40:29 +01:00
|
|
|
$self->debug( 5, ' "', $_, '"' )
|
|
|
|
foreach ( sort keys %ssh_hostname_for );
|
2010-01-29 10:23:08 +00:00
|
|
|
}
|
|
|
|
else {
|
2011-07-01 16:40:29 +01:00
|
|
|
$self->debug( 3, 'Unable to read ',
|
|
|
|
$self->{ssh_config}, ': ', $!, $/ );
|
2010-01-29 10:23:08 +00:00
|
|
|
}
|
2010-01-28 19:10:31 +00:00
|
|
|
}
|
2010-01-12 20:15:11 +00:00
|
|
|
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_hostname {
|
|
|
|
my ($self) = @_;
|
|
|
|
return $self->{hostname};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_username {
|
|
|
|
my ($self) = @_;
|
2010-06-18 23:16:34 +01:00
|
|
|
return $self->{username} || q{};
|
2010-01-12 20:15:11 +00:00
|
|
|
}
|
|
|
|
|
2014-02-01 13:47:42 +00:00
|
|
|
sub get_type {
|
|
|
|
my ($self) = @_;
|
2014-09-19 23:33:08 +01:00
|
|
|
if ( $self->check_ssh_hostname ) {
|
2014-07-02 22:39:36 +01:00
|
|
|
return 'ssh_alias';
|
|
|
|
}
|
2014-02-01 13:47:42 +00:00
|
|
|
return $self->{type} || q{};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_geometry {
|
|
|
|
my ($self) = @_;
|
|
|
|
return $self->{geometry} || q{};
|
|
|
|
}
|
|
|
|
|
2010-01-12 20:15:11 +00:00
|
|
|
sub set_username {
|
|
|
|
my ( $self, $new_username ) = @_;
|
|
|
|
$self->{username} = $new_username;
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_port {
|
|
|
|
my ($self) = @_;
|
2010-06-18 23:16:34 +01:00
|
|
|
return $self->{port} || q{};
|
2010-01-12 20:15:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sub set_port {
|
|
|
|
my ( $self, $new_port ) = @_;
|
|
|
|
$self->{port} = $new_port;
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
2014-02-01 13:47:42 +00:00
|
|
|
sub set_type {
|
|
|
|
my ( $self, $type ) = @_;
|
|
|
|
$self->{type} = $type;
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub set_geometry {
|
|
|
|
my ( $self, $geometry ) = @_;
|
|
|
|
$self->{geometry} = $geometry;
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
2011-06-30 11:12:59 +01:00
|
|
|
sub get_master {
|
|
|
|
my ($self) = @_;
|
|
|
|
return $self->{master} || q{};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub set_master {
|
|
|
|
my ( $self, $new_master ) = @_;
|
|
|
|
$self->{master} = $new_master;
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
2010-01-29 10:23:08 +00:00
|
|
|
sub get_realname {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
if ( !$self->{realname} ) {
|
2014-07-02 22:39:36 +01:00
|
|
|
if ( $self->get_type eq 'ssh_alias' ) {
|
|
|
|
$self->{realname} = $self->{hostname};
|
2010-01-29 10:23:08 +00:00
|
|
|
}
|
|
|
|
else {
|
2014-07-02 22:39:36 +01:00
|
|
|
my $gethost_obj = gethostbyname( $self->{hostname} );
|
|
|
|
|
|
|
|
$self->{realname}
|
|
|
|
= defined($gethost_obj)
|
|
|
|
? $gethost_obj->name()
|
|
|
|
: $self->{hostname};
|
2010-01-29 10:23:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return $self->{realname};
|
|
|
|
}
|
|
|
|
|
2010-01-12 20:15:11 +00:00
|
|
|
sub parse_host_string {
|
|
|
|
my ( $self, $host_string ) = @_;
|
2010-01-29 10:23:08 +00:00
|
|
|
my $parse_string = $host_string;
|
2010-01-12 20:15:11 +00:00
|
|
|
|
2010-01-28 19:10:31 +00:00
|
|
|
$self->debug( 5, $self->loc( 'host_string=" [_1] "', $host_string ), );
|
2010-01-12 20:15:11 +00:00
|
|
|
|
|
|
|
# check for bracketed IPv6 addresses
|
|
|
|
if ($host_string =~ m{
|
|
|
|
\A
|
2014-02-01 13:47:42 +00:00
|
|
|
(?:(.*?)@)? # username@ (optional)
|
|
|
|
\[([\w:]*)\] # [<sequence of chars>]
|
|
|
|
(?::(\d+))? # :port (optional)
|
|
|
|
(?:=(\d+\D\d+\D\d+\D\d))? # =geometry (optional)
|
2010-01-12 20:15:11 +00:00
|
|
|
\z
|
|
|
|
}xms
|
|
|
|
)
|
|
|
|
{
|
2011-07-01 16:40:29 +01:00
|
|
|
$self->debug(
|
|
|
|
5,
|
2014-09-19 23:33:08 +01:00
|
|
|
$self->loc(
|
|
|
|
'bracketed IPv6: u=[_1] h=[_2] p=[_3] g=[_4]',
|
|
|
|
$1, $2, $3, $4
|
|
|
|
),
|
2011-07-01 16:40:29 +01:00
|
|
|
);
|
2010-01-12 20:15:11 +00:00
|
|
|
return __PACKAGE__->new(
|
2010-01-29 10:23:08 +00:00
|
|
|
parse_string => $parse_string,
|
|
|
|
username => $1,
|
|
|
|
hostname => $2,
|
|
|
|
port => $3,
|
2014-02-01 13:47:42 +00:00
|
|
|
geometry => $4,
|
2010-01-29 10:23:08 +00:00
|
|
|
type => 'ipv6',
|
2010-01-12 20:15:11 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
# check for standard IPv4 host.domain/IP address
|
|
|
|
if ($host_string =~ m{
|
|
|
|
\A
|
2014-02-01 13:47:42 +00:00
|
|
|
(?:(.*?)@)? # username@ (optional)
|
|
|
|
([\w\.-]*) # hostname[.domain[.domain] | 123.123.123.123
|
|
|
|
(?::(\d+))? # :port (optional)
|
|
|
|
(?:=(\d+\D\d+\D\d+\D\d+))? # =geometry (optional)
|
2010-01-12 20:15:11 +00:00
|
|
|
\z
|
|
|
|
}xms
|
|
|
|
)
|
|
|
|
{
|
2014-09-19 23:33:08 +01:00
|
|
|
$self->debug(
|
|
|
|
5,
|
|
|
|
$self->loc(
|
|
|
|
'std IPv4: u=[_1] h=[_2] p=[_3] g=[_4]',
|
|
|
|
$1, $2, $3, $4
|
|
|
|
),
|
2011-07-01 16:40:29 +01:00
|
|
|
);
|
2010-01-12 20:15:11 +00:00
|
|
|
return __PACKAGE__->new(
|
2010-01-29 10:23:08 +00:00
|
|
|
parse_string => $parse_string,
|
|
|
|
username => $1,
|
|
|
|
hostname => $2,
|
|
|
|
port => $3,
|
2014-02-01 13:47:42 +00:00
|
|
|
geometry => $4,
|
2010-01-29 10:23:08 +00:00
|
|
|
type => 'ipv4',
|
2010-01-12 20:15:11 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check for unbracketed IPv6 addresses as best we can...
|
2014-02-01 13:47:42 +00:00
|
|
|
my $username = q{};
|
|
|
|
my $geometry = q{};
|
|
|
|
my $port = q{};
|
|
|
|
|
2010-01-12 20:15:11 +00:00
|
|
|
# first, see if there is a username to grab
|
2014-02-01 13:47:42 +00:00
|
|
|
if ( $host_string =~ s/\A(?:(.*?)@)// ) {
|
2010-01-28 19:10:31 +00:00
|
|
|
|
2010-01-12 20:15:11 +00:00
|
|
|
# catch where @ is in host_string but no text before it
|
2014-07-02 22:39:36 +01:00
|
|
|
$username = $1;
|
2010-01-12 20:15:11 +00:00
|
|
|
}
|
|
|
|
|
2014-02-01 13:47:42 +00:00
|
|
|
# check for any geometry settings
|
|
|
|
if ( $host_string =~ s/(?:=(.*?)$)// ) {
|
2014-07-02 22:39:36 +01:00
|
|
|
$geometry = $1;
|
2014-02-01 13:47:42 +00:00
|
|
|
}
|
|
|
|
|
2014-09-19 23:33:08 +01:00
|
|
|
# Check for a '/nnnn' port definition
|
2014-07-02 18:48:06 +01:00
|
|
|
if ( $host_string =~ s!(?:/(\d+)$)!! ) {
|
2014-09-19 23:33:08 +01:00
|
|
|
$port = $1;
|
2014-07-02 18:48:06 +01:00
|
|
|
}
|
|
|
|
|
2010-01-12 20:15:11 +00:00
|
|
|
# use number of colons as a possible indicator
|
|
|
|
my $colon_count = $host_string =~ tr/://;
|
|
|
|
|
|
|
|
# if there are 7 colons assume its a full IPv6 address
|
2014-02-01 13:47:42 +00:00
|
|
|
# if its 8 then assumed full IPv6 address with a port
|
2010-01-12 20:15:11 +00:00
|
|
|
# also catch localhost address here
|
2014-02-01 13:47:42 +00:00
|
|
|
if ( $colon_count == 7 || $colon_count == 8 || $host_string eq '::1' ) {
|
2014-09-19 23:33:08 +01:00
|
|
|
if ( $colon_count == 8 ) {
|
2014-02-01 13:47:42 +00:00
|
|
|
$host_string =~ s/(?::(\d+?))$//;
|
|
|
|
$port = $1;
|
|
|
|
}
|
2011-07-01 16:40:29 +01:00
|
|
|
$self->debug(
|
|
|
|
5,
|
|
|
|
$self->loc(
|
2014-02-01 13:47:42 +00:00
|
|
|
'IPv6: u=[_1] h=[_2] p=[_3] g=[_4]',
|
|
|
|
$username, $host_string, $port, $geometry,
|
2011-07-01 16:40:29 +01:00
|
|
|
),
|
|
|
|
);
|
2010-01-12 20:15:11 +00:00
|
|
|
return __PACKAGE__->new(
|
2010-01-29 10:23:08 +00:00
|
|
|
parse_string => $parse_string,
|
|
|
|
username => $username,
|
|
|
|
hostname => $host_string,
|
2014-02-01 13:47:42 +00:00
|
|
|
port => $port,
|
|
|
|
geometry => $geometry,
|
2010-01-29 10:23:08 +00:00
|
|
|
type => 'ipv6',
|
2010-01-12 20:15:11 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
if ( $colon_count > 1
|
2014-09-19 23:33:08 +01:00
|
|
|
&& $colon_count < 8 )
|
2010-01-12 20:15:11 +00:00
|
|
|
{
|
2010-01-29 10:23:08 +00:00
|
|
|
warn 'Ambiguous host string: "', $host_string, '"', $/;
|
|
|
|
warn 'Assuming you meant "[', $host_string, ']"?', $/;
|
2010-01-12 20:15:11 +00:00
|
|
|
|
2011-07-01 16:40:29 +01:00
|
|
|
$self->debug(
|
|
|
|
5,
|
|
|
|
$self->loc(
|
2014-09-19 23:33:08 +01:00
|
|
|
'Ambiguous IPv6 u=[_1] h=[_2] p=[_3] g=[_4]',
|
|
|
|
$username, $host_string, $port, $geometry,
|
2011-07-01 16:40:29 +01:00
|
|
|
)
|
|
|
|
);
|
2010-01-12 20:15:11 +00:00
|
|
|
|
|
|
|
return __PACKAGE__->new(
|
2010-01-29 10:23:08 +00:00
|
|
|
parse_string => $parse_string,
|
|
|
|
username => $username,
|
|
|
|
hostname => $host_string,
|
2014-02-01 13:47:42 +00:00
|
|
|
port => $port,
|
|
|
|
geometry => $geometry,
|
2010-01-29 10:23:08 +00:00
|
|
|
type => 'ipv6',
|
2010-01-12 20:15:11 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
# if we got this far, we didnt parse the host_string properly
|
2011-07-01 16:40:29 +01:00
|
|
|
croak(
|
|
|
|
App::ClusterSSH::Exception->throw(
|
|
|
|
error => $self->loc(
|
|
|
|
'Unable to parse hostname from "[_1]"', $host_string
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
2010-01-12 20:15:11 +00:00
|
|
|
}
|
|
|
|
|
2010-01-28 19:10:31 +00:00
|
|
|
sub check_ssh_hostname {
|
|
|
|
my ( $self, ) = @_;
|
|
|
|
|
2011-07-01 16:40:29 +01:00
|
|
|
$self->debug( 4, 'Checking ssh hosts for hostname ',
|
|
|
|
$self->get_hostname );
|
2010-01-28 19:10:31 +00:00
|
|
|
|
2010-01-29 10:23:08 +00:00
|
|
|
if ( $ssh_hostname_for{ $self->get_hostname } ) {
|
2010-01-28 19:10:31 +00:00
|
|
|
return 1;
|
2010-01-29 10:23:08 +00:00
|
|
|
}
|
|
|
|
else {
|
2010-01-28 19:10:31 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-01-12 20:15:11 +00:00
|
|
|
use overload (
|
|
|
|
q{""} => sub {
|
|
|
|
my ($self) = @_;
|
|
|
|
return $self->{hostname};
|
|
|
|
},
|
|
|
|
fallback => 1,
|
|
|
|
);
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
|
|
|
=pod
|
|
|
|
|
2010-06-20 17:51:47 +01:00
|
|
|
=head1 NAME
|
2010-01-12 20:15:11 +00:00
|
|
|
|
2013-02-14 22:11:21 +00:00
|
|
|
ClusterSSH::Host - Object representing a host.
|
2010-01-12 20:15:11 +00:00
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
use ClusterSSH::Host;
|
|
|
|
|
|
|
|
my $host = ClusterSSH::Host->new({
|
|
|
|
hostname => 'hostname',
|
|
|
|
});
|
|
|
|
my $host = ClusterSSH::Host->parse_host_string('username@hostname:1234');
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
|
|
|
Object representing a host. Include details to contact the host such as
|
|
|
|
hostname/ipaddress, username and port.
|
|
|
|
|
|
|
|
=head1 METHODS
|
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
|
|
|
=item $host=ClusterSSH::Host->new ({ hostname => 'hostname' })
|
|
|
|
|
|
|
|
Create a new host object. 'hostname' is a required arg, 'username' and
|
|
|
|
'port' are optional. Raises exception if an error occurs.
|
|
|
|
|
|
|
|
=item $host->get_hostname
|
|
|
|
|
|
|
|
=item $host->get_username
|
|
|
|
|
|
|
|
=item $host->get_port
|
|
|
|
|
2011-06-30 11:12:59 +01:00
|
|
|
=item $host->get_master
|
|
|
|
|
2014-06-29 12:49:37 +01:00
|
|
|
=item $host->get_geometry
|
|
|
|
|
|
|
|
=item $host->get_type
|
|
|
|
|
2010-01-12 20:15:11 +00:00
|
|
|
Return specific details about the host
|
|
|
|
|
|
|
|
=item $host->set_username
|
|
|
|
|
|
|
|
=item $host->set_port
|
|
|
|
|
2011-06-30 11:12:59 +01:00
|
|
|
=item $host->set_master
|
|
|
|
|
2014-06-29 12:49:37 +01:00
|
|
|
=item $host->set_geometry
|
|
|
|
|
|
|
|
=item $host->set_type
|
|
|
|
|
2010-01-12 20:15:11 +00:00
|
|
|
Set specific details about the host after its been created.
|
|
|
|
|
2010-06-18 23:16:34 +01:00
|
|
|
=item get_realname
|
|
|
|
|
|
|
|
If the server name provided is not an IP address (either IPv4 or IPv6)
|
|
|
|
attempt to resolve it and retun the discovered names.
|
|
|
|
|
|
|
|
=item get_givenname
|
|
|
|
|
|
|
|
Alias to get_hostname, for use when C< get_realname > might return something
|
|
|
|
different
|
|
|
|
|
2010-01-12 20:15:11 +00:00
|
|
|
=item parse_host_string
|
|
|
|
|
|
|
|
Given a host string, returns a host object. Parses hosts such as
|
|
|
|
|
2010-06-18 23:16:34 +01:00
|
|
|
=item check_ssh_hostname
|
|
|
|
|
|
|
|
Check the objects hostname to see whether or not it may be configured within
|
|
|
|
the users F< $HOME/.ssh/config > configuration file
|
|
|
|
|
2010-01-12 20:15:11 +00:00
|
|
|
=over 4
|
|
|
|
|
|
|
|
=item host
|
|
|
|
|
|
|
|
=item 192.168.0.1
|
|
|
|
|
|
|
|
=item user@host
|
|
|
|
|
|
|
|
=item user@192.168.0.1
|
|
|
|
|
|
|
|
=item host:port
|
|
|
|
|
|
|
|
=item [1234:1234:1234::4567]:port
|
|
|
|
|
|
|
|
=item 1234:1234:1234::4567
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
and so on. Cope with IPv4 and IPv6 addresses - raises a warning if the
|
|
|
|
IPv6 address is ambiguous (i.e. in the last example, is the 4567 part of
|
|
|
|
the IPv6 address or a port definition?) and assumes it is part of address.
|
|
|
|
Use brackets to avoid seeing warning.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
2010-06-20 20:23:41 +01:00
|
|
|
Duncan Ferguson, C<< <duncan_j_ferguson at yahoo.co.uk> >>
|
2010-01-12 20:15:11 +00:00
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT
|
|
|
|
|
2010-06-20 20:23:41 +01:00
|
|
|
Copyright 1999-2010 Duncan Ferguson.
|
2010-01-12 20:15:11 +00:00
|
|
|
|
2010-06-20 20:23:41 +01:00
|
|
|
This program is free software; you can redistribute it and/or modify it
|
|
|
|
under the terms of either: the GNU General Public License as published
|
|
|
|
by the Free Software Foundation; or the Artistic License.
|
2010-01-12 20:15:11 +00:00
|
|
|
|
2010-06-20 20:23:41 +01:00
|
|
|
See http://dev.perl.org/licenses/ for more information.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
1;
|