<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;"># ssh-lib.pl
# Common functions for manipulating the users .ssh directory files

BEGIN { push(@INC, ".."); };
use WebminCore;
&amp;init_config();
&amp;switch_to_remote_user();

$ssh_directory = "$remote_user_info[7]/.ssh";
$ssh_client_config = "$ssh_directory/config";
$authorized_keys = "$ssh_directory/authorized_keys";
$known_hosts = "$ssh_directory/known_hosts";

@ssh_key_types = ( 'rsa1', 'rsa', 'dsa' );

# find_value(name, &amp;config)
sub find_value
{
foreach $c (@{$_[1]}) {
	if (lc($c-&gt;{'name'}) eq lc($_[0])) {
		return wantarray ? @{$c-&gt;{'values'}} : $c-&gt;{'values'}-&gt;[0];
		}
	}
return wantarray ? ( ) : undef;
}

# find(value, &amp;config)
sub find
{
local @rv;
foreach $c (@{$_[1]}) {
	if (lc($c-&gt;{'name'}) eq lc($_[0])) {
		push(@rv, $c);
		}
	}
return wantarray ? @rv : $rv[0];
}

# save_directive(name, &amp;config, [value*])
sub save_directive
{
local @o = &amp;find($_[0], $_[1]);
local @n = grep { defined($_) } @_[2..@_-1];
local $lref = &amp;read_file_lines($_[1]-&gt;[0]-&gt;{'file'});
local $id = ("\t" x $_[1]-&gt;[0]-&gt;{'indent'});
local $i;
for($i=0; $i&lt;@o || $i&lt;@n; $i++) {
	if ($o[$i] &amp;&amp; $n[$i]) {
		# Replacing a line
		$lref-&gt;[$o[$i]-&gt;{'line'}] = "$id$_[0] $n[$i]";
		}
	elsif ($o[$i]) {
		# Removing a line
		splice(@$lref, $o[$i]-&gt;{'line'}, 1);
		foreach $c (@{$_[1]}) {
			if ($c-&gt;{'line'} &gt; $o[$i]-&gt;{'line'}) {
				$c-&gt;{'line'}--;
				}
			}
		}
	elsif ($n[$i]) {
		# Adding a line
		local $ll = $_[1]-&gt;[@{$_[1]}-1]-&gt;{'line'};
		splice(@$lref, $ll+1, 0, "$id$_[0] $n[$i]");
		}
	}
}

# scmd(double)
sub scmd
{
if ($cmd_count % 2 == 0) {
	print "&lt;tr&gt;\n";
	}
elsif ($_[0]) {
	print "&lt;td colspan=2&gt;&lt;/td&gt; &lt;/tr&gt;\n";
	print "&lt;tr&gt;\n";
	$cmd_count = 0;
	}
$cmd_count += ($_[0] ? 2 : 1);
}

# ecmd()
sub ecmd
{
if ($cmd_count % 2 == 0) {
	print "&lt;/tr&gt;\n";
	}
}



# get_client_config()
# Returns a list of structures, one for each host
sub get_client_config
{
local @rv = ( { 'dummy' =&gt; 1,
		'indent' =&gt; 0,
		'file' =&gt; $ssh_client_config,
		'line' =&gt; -1,
		'eline' =&gt; -1 } );
local $host;
local $lnum = 0;
open(CLIENT, $ssh_client_config);
while(&lt;CLIENT&gt;) {
	s/\r|\n//g;
	s/^\s*#.*$//g;
	s/^\s*//g;
	local ($name, @values) = split(/\s+/, $_);
	if (lc($name) eq 'host') {
		# Start of new host
		$host = { 'name' =&gt; $name,
			  'values' =&gt; \@values,
			  'file' =&gt; $ssh_client_config,
			  'line' =&gt; $lnum,
			  'eline' =&gt; $lnum,
			  'members' =&gt; [ { 'dummy' =&gt; 1,
					   'indent' =&gt; 1,
					   'file' =&gt; $ssh_client_config,
					   'line' =&gt; $lnum } ] };
		push(@rv, $host);
		}
	elsif ($name) {
		# A directive inside a host
		local $dir = { 'name' =&gt; $name,
			       'values' =&gt; \@values,
			       'file' =&gt; $ssh_client_config,
			       'line' =&gt; $lnum };
		push(@{$host-&gt;{'members'}}, $dir);
		$host-&gt;{'eline'} = $lnum;
		}
	$lnum++;
	}
close(CLIENT);
return \@rv;
}

# create_host(&amp;host)
sub create_host
{
local $lref = &amp;read_file_lines($ssh_client_config);
$_[0]-&gt;{'line'} = $_[0]-&gt;{'eline'} = scalar(@$lref);
push(@$lref, "Host ".join(" ", @{$_[0]-&gt;{'values'}}));
$_[0]-&gt;{'members'} = [ { 'dummy' =&gt; 1,
			 'indent' =&gt; 1,
			 'file' =&gt; $ssh_client_config,
			 'line' =&gt; $_[0]-&gt;{'line'} } ];
}

# modify_host(&amp;host)
sub modify_host
{
local $lref = &amp;read_file_lines($ssh_client_config);
$lref-&gt;[$_[0]-&gt;{'line'}] = "Host ".join(" ", @{$_[0]-&gt;{'values'}});
}

# delete_host(&amp;host)
sub delete_host
{
local $lref = &amp;read_file_lines($ssh_client_config);
splice(@$lref, $_[0]-&gt;{'line'}, $_[0]-&gt;{'eline'} - $_[0]-&gt;{'line'} + 1);
}

# list_auths()
# Returns a list of authorized key structures
sub list_auths
{
local @rv;
local $lnum = 0;
open(AUTHS, $authorized_keys);
while(&lt;AUTHS&gt;) {
	s/\r|\n//g;
	s/#.*$//;
	if (/^((.*)\s+)?(\d+)\s+(\d+)\s+(\d+)\s+(\S+)$/) {
		# SSH1 style line
		local $auth = { 'name' =&gt; $6,
				'key' =&gt; $5,
				'exp' =&gt; $4,
				'bits' =&gt; $3,
				'opts' =&gt; $2,
				'type' =&gt; 1,
				'line' =&gt; $lnum,
				'index' =&gt; scalar(@rv) };
		$auth-&gt;{'opts'} =~ s/\s+$//;
		push(@rv, $auth);
		}
	elsif (/^((.*)\s+)?([a-z]\S+)\s+(\S+)\s+(\S+)/) {
		# SSH2 style line
		local $auth = { 'name' =&gt; $5,
				'key' =&gt; $4,
				'opts' =&gt; $2,
				'keytype' =&gt; $3,
				'type' =&gt; 2,
				'line' =&gt; $lnum,
				'index' =&gt; scalar(@rv) };
		$auth-&gt;{'opts'} =~ s/\s+$//;
		push(@rv, $auth);
		}
	$lnum++;
	}
close(AUTHS);
return @rv;
}

# create_auth(&amp;auth)
sub create_auth
{
local $lref = &amp;read_file_lines($authorized_keys);
push(@$lref, &amp;auth_line($_[0]));
&amp;flush_file_lines();
}

# delete_auth(&amp;auth)
sub delete_auth
{
local $lref = &amp;read_file_lines($authorized_keys);
splice(@$lref, $_[0]-&gt;{'line'}, 1);
&amp;flush_file_lines();
}

# modify_auth(&amp;auth)
sub modify_auth
{
local $lref = &amp;read_file_lines($authorized_keys);
$lref-&gt;[$_[0]-&gt;{'line'}] = &amp;auth_line($_[0]);
&amp;flush_file_lines();
}

# parse_options(string, &amp;opts)
sub parse_options
{
local $opts = $_[0];
while($opts =~ /^([^=\s,]+)=\"([^\"]*)\",?(.*)$/ ||
      $opts =~ /^([^=\s,]+)=([^=\s,]+),?(.*)$/ ||
      $opts =~ /^([^=\s,]+)(),?(.*)$/) {
	push(@{$_[1]-&gt;{$1}}, $2);
	$opts = $3;
	}
}

# join_options(&amp;opts)
sub join_options
{
local @rv;
foreach $o (keys %{$_[0]}) {
	foreach $ov (@{$_[0]-&gt;{$o}}) {
		if (defined($ov)) {
			push(@rv, "$o=\"$ov\"");
			}
		else {
			push(@rv, $o);
			}
		}
	}
return join(",", @rv);
}

sub auth_line
{
if ($_[0]-&gt;{'type'} == 2) {
	# SSH 2 format line
	return join(" ", $_[0]-&gt;{'opts'} ? ( $_[0]-&gt;{'opts'} ) : ( ),
			 $_[0]-&gt;{'keytype'}, $_[0]-&gt;{'key'}, $_[0]-&gt;{'name'} );
	}
else {
	# SSH 1 format line
	return join(" ", $_[0]-&gt;{'opts'} ? ( $_[0]-&gt;{'opts'} ) : ( ),
			 $_[0]-&gt;{'bits'}, $_[0]-&gt;{'exp'}, $_[0]-&gt;{'key'}, $_[0]-&gt;{'name'} );
	}
}

# list_knowns()
# Returns a list of known host structures
sub list_knowns
{
local @rv;
local $lnum = 0;
open(KNOWNS, $known_hosts);
while(&lt;KNOWNS&gt;) {
	s/\r|\n//g;
	s/#.*$//;
	#Handle hashed type 2 keys
	if (/^\|1\|(\S+=)\|(\S+=)\s(.+)\s(\S+=)\s*(.*)$/) {
		local $known = { 'comment' =&gt; $5,
				 'key' =&gt; $4,
				 'salt' =&gt; $1,
				 'hash' =&gt; $2,
				 'type' =&gt; $3,
				 'hosts' =&gt; [ split(/,/, '*HASHED*') ],
				 'line' =&gt; $lnum,
				 'index' =&gt; scalar(@rv) };
		push(@rv, $known);
		}
	#Handle hashed type 1 keys
	elsif (/^\|1\|(\S+=)\|(\S+=)\s(\d+)\s(\d+)\s(\S+)\s*(.*)$/) {
		local $known = { 'comment' =&gt; $6,
				 'key' =&gt; $5,
				 'type' =&gt; 'ssh-rsa1',
				 'salt' =&gt; $1,
				 'hash' =&gt; $2,
				 'exp' =&gt; $4,
				 'bits' =&gt; $3,
				 'hosts' =&gt; [ split(/,/, '*HASHED*') ],
				 'line' =&gt; $lnum,
				 'index' =&gt; scalar(@rv) };
		push(@rv, $known);
		}
	#Handle type 2 keys
	elsif (/^(\S+)\s+(.+)\s(\S+=)\s*(.*)$/) {
		local $known = { 'comment' =&gt; $4,
				 'key' =&gt; $3,
				 'type' =&gt; $2,
				 'hosts' =&gt; [ split(/,/, $1) ],
				 'line' =&gt; $lnum,
				 'index' =&gt; scalar(@rv) };
		push(@rv, $known);
		}
	#Handle type 1 keys
	elsif (/^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s*(.*)$/) {
		local $known = { 'comment' =&gt; $5,
				 'key' =&gt; $4,
				 'type' =&gt; 'ssh-rsa1',
				 'exp' =&gt; $3,
				 'bits' =&gt; $2,
				 'hosts' =&gt; [ split(/,/, $1) ],
				 'line' =&gt; $lnum,
				 'index' =&gt; scalar(@rv) };
		push(@rv, $known);
		}
	$lnum++;
	}
close(KNOWNS);
return @rv;
}

# create_known(&amp;known)
sub create_known
{
local $lref = &amp;read_file_lines($known_hosts);
push(@$lref, &amp;known_line($_[0]));
&amp;flush_file_lines();
}

# delete_known(&amp;known)
sub delete_known
{
local $lref = &amp;read_file_lines($known_hosts);
splice(@$lref, $_[0]-&gt;{'line'}, 1);
&amp;flush_file_lines();
}

# modify_known(&amp;known)
sub modify_known
{
local $lref = &amp;read_file_lines($known_hosts);
$lref-&gt;[$_[0]-&gt;{'line'}] = &amp;known_line($_[0]);
&amp;flush_file_lines();
}

sub known_line
{
if ($_[0]-&gt;{'hash'}) {
	if ($_[0]-&gt;{'type'} eq 'ssh-rsa1') {
		return join(" ", '|1|' . $_[0]-&gt;{'salt'} . '|' . $_[0]-&gt;{'hash'}, $_[0]-&gt;{'bits'}, $_[0]-&gt;{'exp'},
			$_[0]-&gt;{'key'}, $_[0]-&gt;{'comment'} ? ( $_[0]-&gt;{'comment'} ) : ( ) );
		}
	else {
		return join(" ", '|1|' . $_[0]-&gt;{'salt'} . '|' . $_[0]-&gt;{'hash'}, $_[0]-&gt;{'type'},
			$_[0]-&gt;{'key'}, $_[0]-&gt;{'comment'} ? ( $_[0]-&gt;{'comment'} ) : ( ) );
		}
	}
else {
	if ($_[0]-&gt;{'type'} eq 'ssh-rsa1') {
		return join(" ", join(",", @{$_[0]-&gt;{'hosts'}}), $_[0]-&gt;{'bits'}, $_[0]-&gt;{'exp'},
			$_[0]-&gt;{'key'}, $_[0]-&gt;{'comment'} ? ( $_[0]-&gt;{'comment'} ) : ( ) );
		}
	else {
		return join(" ", join(",", @{$_[0]-&gt;{'hosts'}}), $_[0]-&gt;{'type'},
			$_[0]-&gt;{'key'}, $_[0]-&gt;{'comment'} ? ( $_[0]-&gt;{'comment'} ) : ( ) );
		}
	}
}

# get_ssh_type()
# Returns either 'openssh' or 'ssh'
sub get_ssh_type
{
local $out = `ssh -V 2&gt;&amp;1 &lt;/dev/null`;
return $out =~ /openssh/i ? 'openssh' :
       $out =~ /Sun_SSH/i ? 'openssh' :
			    'ssh';
}

# get_ssh_version()
# Returns the SSH version number
sub get_ssh_version
{
local $out = `ssh -V 2&gt;&amp;1 &lt;/dev/null`;
if ($out =~ /(sshd\s+version\s+([0-9\.]+))/i ||
    $out =~ /(ssh\s+secure\s+shell\s+([0-9\.]+))/i) {
	# Classic commercial SSH
	return $2;
	}
elsif ($out =~ /(OpenSSH.([0-9\.]+))/i) {
	# OpenSSH .. assume all versions are supported
	return $2;
	}
elsif ($out =~ /(Sun_SSH_([0-9\.]+))/i) {
	# Solaris 9 SSH is actually OpenSSH 2.x
	return 2.0;
	}
elsif (($out = $config{'sshd_version'}) &amp;&amp; ($out =~ /(Sun_SSH_([0-9\.]+))/i)) {
	# Probably Solaris 10 SSHD that didn't display version.  Use it.
	return 2.0;
	}
else {
	return undef;
	}
}

sub yes_no_default_radio
{
local ($name, $value) = @_;
return &amp;ui_radio($name, lc($value) eq 'yes' ? 1 :
			lc($value) eq 'no' ? 0 : 2,
		 [ [ 1, $text{'yes'} ], [ 0, $text{'no'} ],
		   [ 2, $text{'default'} ] ]);
}

# list_ssh_keys()
# Returns a list of the current user's private SSH keys
sub list_ssh_keys
{
local @rv;
foreach my $t ([ 'identity', 'rsa1' ], [ 'id_dsa', 'dsa' ],
	       [ 'id_rsa', 'rsa']) {
	local $private_file = "$ssh_directory/$t-&gt;[0]";
	if (-r $private_file) {
		push(@rv, { 'type' =&gt; $t-&gt;[1],
			    'private_file' =&gt; $private_file,
			    'public_file' =&gt; $private_file.".pub",
			  });
		}
	}
return @rv;
}

1;

</pre></body></html>