#
# SMArT
#
# Handle /settings/ requests
#
# Copyright 2001 Wilmer van der Gaast (lintux@lintux.cx)
#
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
sub html_escape( $ )
{
my $s = $_[0];
$s = '' unless defined $s;
$s =~ s/&/&/g;
$s =~ s/</g;
$s =~ s/>/>/g;
$s =~ s/"/"/g;
return $s;
}
sub settings_nav_bar()
{
return <<'EOF_NAV';
EOF_NAV
}
sub delete_confirm_attr( $ )
{
my $what = html_escape( $_[0] );
return ' onclick="return confirm(\'Delete ' . $what . '?\')"';
}
sub advanced_link_row( $$ )
{
my( $href, $label ) = @_;
return qq|\t\n\t\t$label \n\t \n|;
}
sub advanced_category()
{
my $cat = $c[2];
$cat = 'all' if ! defined( $cat ) || $cat eq '';
if( $cat =~ /^(devices|security|users|queues|configh|stations|network|all)$/ )
{
return $cat;
}
return 'all';
}
sub advanced_section_visible( $$ )
{
my( $cat, $section ) = @_;
return 1 if $cat eq 'all';
return 1 if $cat eq 'devices' && $section =~ /^(5)$/;
return 1 if $cat eq 'security' && $section =~ /^(8)$/;
return 1 if $cat eq 'users' && $section =~ /^(17)$/;
return 1 if $cat eq 'queues' && $section =~ /^(18|22)$/;
return 1 if $cat eq 'configh' && $section =~ /^(50|80)$/;
return 1 if $cat eq 'stations' && $section =~ /^(400|401|402)$/;
return 1 if $cat eq 'network' && $section =~ /^(310)$/;
return 0;
}
sub advanced_title( $ )
{
my $cat = $_[0];
return 'Advanced device settings' if $cat eq 'devices';
return 'Advanced security settings' if $cat eq 'security';
return 'Advanced user / bindery settings' if $cat eq 'users';
return 'Advanced print queue settings' if $cat eq 'queues';
return 'Advanced precompiled / path settings' if $cat eq 'configh';
return 'Advanced station access settings' if $cat eq 'stations';
return 'Advanced network watchdog settings' if $cat eq 'network';
return 'Advanced settings';
}
sub advanced_hidden_category_input( $ )
{
my $cat = $_[0];
return '' if ! defined( $cat ) || $cat eq '' || $cat eq 'all';
return ' ' . "\n";
}
sub cfg_int( $ )
{
my $v = $_[0];
$v = '' unless defined $v;
$v =~ s/^\s+//;
$v =~ s/\s+$//;
return 0 if $v eq '';
return oct( $v ) if $v =~ /^0x[0-9a-f]+$/i;
return int( $v );
}
sub cfg_checked( $$ )
{
my( $value, $mask ) = @_;
return ( cfg_int( $value ) & $mask ) ? ' CHECKED' : '';
}
sub html_selected( $$ )
{
return ( defined $_[0] && defined $_[1] && $_[0] eq $_[1] ) ? ' SELECTED' : '';
}
sub option_012( $ )
{
my $v = $_[0];
$v = '0' unless defined $v && $v ne '';
return
'0 - ignore station file / always allow ' . "\n" .
'1 - station file is exclude list ' . "\n" .
'2 - station file is include list ' . "\n";
}
sub getconfiglines_without_section( $ )
{
my @lines = getconfig( $_[0] );
foreach my $line ( @lines )
{
$line =~ s/^\s*$_[0]\s+//;
}
return @lines;
}
$settings_nav_bar = settings_nav_bar();
sub kernel_network_interfaces()
{
my %interfaces = ();
if( opendir( my $dh, '/sys/class/net' ) )
{
foreach my $ifname ( readdir( $dh ) )
{
next if $ifname =~ /^\./;
next if $ifname =~ /[^-_\.A-Za-z0-9]/;
$interfaces{$ifname} = 1;
}
closedir( $dh );
}
return %interfaces;
}
sub ipx_enabled_interfaces()
{
my %ipx_interfaces = ();
# Modern Linux IPX procfs format:
# Network Node_Address Primary Device Frame_Type
# AC100B98 000000000001 Yes Internal None
# 00000022 508140F6AC45 No enp46s0u1u3u3 802.2
if( open( my $fh, '<', '/proc/net/ipx/interface' ) )
{
while( my $line = <$fh> )
{
chomp( $line );
$line =~ s/^\s+//;
$line =~ s/\s+$//;
next if $line eq '';
next if $line =~ /^Network\s+Node_Address\s+Primary\s+Device\s+Frame_Type/i;
my @fields = split( /\s+/, $line );
# Device is the fourth column. Do not scan all tokens, otherwise
# Network/Node/Frame_Type values can accidentally be treated as names.
next if scalar( @fields ) < 5;
my $dev = $fields[3];
next if ! defined( $dev );
next if $dev eq '';
next if lc( $dev ) eq 'internal';
next if $dev =~ /[^-_\.A-Za-z0-9]/;
# Only show real Linux network interfaces.
next if ! -e '/sys/class/net/' . $dev;
$ipx_interfaces{$dev} = 1;
}
close( $fh );
}
# Fallback for older distributions/tools, if present. This parser is kept
# deliberately conservative and still verifies /sys/class/net/.
if( open( my $fh, '<', '/proc/net/ipx_interfaces' ) )
{
while( my $line = <$fh> )
{
chomp( $line );
$line =~ s/^\s+//;
$line =~ s/\s+$//;
next if $line eq '';
next if $line =~ /^(Network|Net|Address|Node|Interface|Device)\b/i;
foreach my $dev ( split( /\s+/, $line ) )
{
next if ! defined( $dev );
next if $dev eq '';
next if lc( $dev ) eq 'internal';
next if $dev =~ /[^-_\.A-Za-z0-9]/;
next if ! -e '/sys/class/net/' . $dev;
$ipx_interfaces{$dev} = 1;
}
}
close( $fh );
}
return sort keys %ipx_interfaces;
}
sub network_interface_options( $ )
{
my $current = $_[0];
my @interfaces = ();
my %seen = ();
my $html = '';
$current = '' unless defined $current;
# Only show interfaces where IPX is currently active. The current value is
# still preserved below, so an existing config entry is not lost if the
# interface is temporarily down or IPX is not loaded at display time.
foreach my $ifname ( ipx_enabled_interfaces() )
{
next if $seen{$ifname};
push( @interfaces, $ifname );
$seen{$ifname} = 1;
}
# Keep special/manual values usable even if they are not real kernel interfaces.
foreach my $ifname ( '*', 'auto', $current )
{
next if !defined( $ifname ) || $ifname eq '';
next if $seen{$ifname};
push( @interfaces, $ifname );
$seen{$ifname} = 1;
}
foreach my $ifname ( @interfaces )
{
my $selected = ( $ifname eq $current ) ? ' SELECTED' : '';
my $label = $ifname;
$label = '* (all IPX interfaces)' if $ifname eq '*';
$label = 'auto' if $ifname eq 'auto';
$html .= '\t\t\t\t' . html_escape( $label ) . ' \n';
}
return $html;
}
sub url_escape( $ )
{
my $s = $_[0];
$s = '' unless defined $s;
$s =~ s/([^A-Za-z0-9_\.\-])/sprintf("%%%02X", ord($1))/eg;
return $s;
}
sub js_escape( $ )
{
my $s = $_[0];
$s = '' unless defined $s;
$s =~ s/\\/\\\\/g;
$s =~ s/'/\\'/g;
$s =~ s/\r/\\r/g;
$s =~ s/\n/\\n/g;
return $s;
}
sub smart_find_executable( @ )
{
foreach my $p ( @_ )
{
next if ! defined( $p ) || $p eq '';
return $p if -x $p;
}
return '';
}
sub sanitize_cups_printer_name( $ )
{
my $s = $_[0];
$s = '' unless defined $s;
$s =~ s/[^-_\.A-Za-z0-9]//g;
return $s;
}
sub cups_queue_name( $ )
{
my $s = sanitize_cups_printer_name( $_[0] );
$s = uc( $s );
$s =~ s/[^A-Z0-9_\-]/_/g;
$s = 'PRINTQ' if $s eq '';
$s = substr( $s, 0, 47 );
return $s;
}
sub cups_print_command( $ )
{
my $printer = sanitize_cups_printer_name( $_[0] );
my $template = $smart_cups_print_command_template;
$template = '/usr/bin/lp -d %p -' unless defined( $template ) && $template ne '';
$template =~ s/%p/$printer/g;
return $template;
}
sub cups_add_printer( $$$ )
{
my( $printers, $seen, $name ) = @_;
return if ! defined( $name );
$name =~ s/^\s+//;
$name =~ s/\s+$//;
return if $name eq '';
return if $name =~ /[^-_\.A-Za-z0-9]/;
my $queue = cups_queue_name( $name );
my $name_key = lc( $name );
my $queue_key = lc( $queue );
# Deduplicate by both the original CUPS name and the generated MARS_NWE
# queue name. This avoids duplicates when the same printer is discovered
# through lpstat -e and a localized lpstat -p fallback.
return if $seen->{'name:' . $name_key};
return if $seen->{'queue:' . $queue_key};
push( @$printers, {
name => $name,
queue => $queue,
command => cups_print_command( $name ),
} );
$seen->{'name:' . $name_key} = 1;
$seen->{'queue:' . $queue_key} = 1;
}
sub cups_printers()
{
my @printers = ();
my %seen = ();
return @printers if ! defined( $smart_cups_enable ) || !$smart_cups_enable;
return @printers if ! defined( $smart_cups_lpstat_path ) || $smart_cups_lpstat_path eq '';
return @printers if ! -x $smart_cups_lpstat_path;
# Prefer "lpstat -e": it prints only destination names and is language
# independent. If it returns at least one printer, do not run fallback
# parsers, because some CUPS versions/locales can expose aliases there.
if( open( my $fh, '-|', $smart_cups_lpstat_path, '-e' ) )
{
while( my $line = <$fh> )
{
chomp( $line );
$line =~ s/^\s+//;
$line =~ s/\s+$//;
next if $line eq '';
next if $line =~ /[\r\n]/;
cups_add_printer( \@printers, \%seen, $line );
}
close( $fh );
}
return @printers if scalar( @printers ) > 0;
# Fallback: "lpstat -v" is also mostly language independent because the
# destination name follows "for :" on many systems.
if( open( my $fh, '-|', $smart_cups_lpstat_path, '-v' ) )
{
while( my $line = <$fh> )
{
chomp( $line );
if( $line =~ /\bfor\s+([^:\s]+)\s*:/i )
{
cups_add_printer( \@printers, \%seen, $1 );
}
}
close( $fh );
}
return @printers if scalar( @printers ) > 0;
# Last fallback: parse localized "lpstat -p" output. English starts with
# "printer ", German starts with "Drucker ".
if( open( my $fh, '-|', $smart_cups_lpstat_path, '-p' ) )
{
while( my $line = <$fh> )
{
chomp( $line );
if( $line =~ /^(?:printer|Drucker)\s+(\S+)/i )
{
cups_add_printer( \@printers, \%seen, $1 );
}
}
close( $fh );
}
return @printers;
}
sub cups_import_rows()
{
my $html = '';
my @printers = cups_printers();
if( scalar( @printers ) == 0 )
{
return qq|\t\n\t\tCUPS printers detected on host \n\t \n\t\n\t\tCUPS support is enabled, but no printers were returned by lpstat. \n\t \n|;
}
$html .= qq|\t\n\t\tCUPS printers detected on host \n\t \n|;
foreach my $printer ( @printers )
{
my $name = ref( $printer ) eq 'HASH' ? $printer->{name} : $printer;
my $queue = ref( $printer ) eq 'HASH' ? $printer->{queue} : cups_queue_name( $printer );
my $cmd = ref( $printer ) eq 'HASH' ? $printer->{command} : cups_print_command( $printer );
my $href = '/settings/queues/add_new?cups_printer=' . url_escape( $name );
$html .= "\t\n";
$html .= "\t\t" . html_escape( $name ) . " Queue: " . html_escape( $queue ) . " Command: " . html_escape( $cmd ) . " \n";
$html .= "\t\tAdd as print queue \n";
$html .= "\t \n";
}
return $html;
}
sub cups_select_html( $ )
{
my $current = sanitize_cups_printer_name( $_[0] );
my @printers = cups_printers();
my $html = '';
my $js = '';
return ( '', '' ) if scalar( @printers ) == 0;
$html .= qq|\n|;
$html .= qq|\t\t\t\t-- choose CUPS printer -- \n|;
foreach my $printer ( @printers )
{
my $name = ref( $printer ) eq 'HASH' ? $printer->{name} : $printer;
my $queue = ref( $printer ) eq 'HASH' ? $printer->{queue} : cups_queue_name( $printer );
my $cmd = ref( $printer ) eq 'HASH' ? $printer->{command} : cups_print_command( $printer );
my $sel = ( $name eq $current ) ? ' SELECTED' : '';
$html .= qq|\t\t\t\t| . html_escape( $name ) . qq| \n|;
$js .= "smartCupsCommands['" . js_escape( $name ) . "'] = '" . js_escape( $cmd ) . "';\n";
$js .= "smartCupsQueues['" . js_escape( $name ) . "'] = '" . js_escape( $queue ) . "';\n";
}
$html .= qq|\t\t\t |;
return ( $html, $js );
}
sub ipx_interface_info( $ )
{
my $want = $_[0];
my %info = ();
return %info if ! defined( $want ) || $want eq '';
if( open( my $fh, '<', '/proc/net/ipx/interface' ) )
{
while( my $line = <$fh> )
{
chomp( $line );
$line =~ s/^\s+//;
$line =~ s/\s+$//;
next if $line eq '';
next if $line =~ /^Network\s+Node_Address\s+Primary\s+Device\s+Frame_Type/i;
my @fields = split( /\s+/, $line );
next if scalar( @fields ) < 5;
my $network = $fields[0];
my $dev = $fields[3];
my $frame = $fields[4];
next if ! defined( $dev ) || $dev ne $want;
next if lc( $dev ) eq 'internal';
$info{network} = $network;
$info{frame} = $frame;
last;
}
close( $fh );
}
return %info;
}
sub ipx_device_import_rows()
{
my $html = '';
my @ifaces = ipx_enabled_interfaces();
if( scalar( @ifaces ) == 0 )
{
return qq|\t\n\t\tIPX interfaces detected on host \n\t \n\t\n\t\tNo active IPX interfaces were found in /proc/net/ipx/interface . \n\t \n|;
}
$html .= qq|\t\n\t\tIPX interfaces detected on host \n\t \n|;
foreach my $iface ( @ifaces )
{
my %info = ipx_interface_info( $iface );
my $net = $info{network};
my $frame = $info{frame};
my $delay = 1;
$net = '0x0' if ! defined( $net ) || $net eq '';
$frame = '802.2' if ! defined( $frame ) || $frame eq '' || lc( $frame ) eq 'none';
my $href = '/settings/devices/add_new?ipx_iface=' . url_escape( $iface );
$html .= "\t\n";
$html .= "\t\t" . html_escape( $iface ) . " Config line: 4 " . html_escape( $net ) . " " . html_escape( $iface ) . " " . html_escape( $frame ) . " " . html_escape( $delay ) . " \n";
$html .= "\t\tAdd as device \n";
$html .= "\t \n";
}
return $html;
}
sub ipx_device_defaults_from_query()
{
my %defaults = ();
$defaults{number} = '';
$defaults{interface} = '';
$defaults{frametype} = '802.2';
$defaults{delay} = 1;
if( defined( $p{ipx_iface} ) && $p{ipx_iface} ne '' )
{
my $iface = $p{ipx_iface};
$iface =~ s/[^-_\.A-Za-z0-9]//g;
my %info = ipx_interface_info( $iface );
$defaults{interface} = $iface;
$defaults{number} = defined( $info{network} ) && $info{network} ne '' ? $info{network} : '0x0';
$defaults{frametype} = defined( $info{frame} ) && $info{frame} ne '' && lc( $info{frame} ) ne 'none' ? $info{frame} : '802.2';
$defaults{delay} = 1;
}
return %defaults;
}
sub smart_bindery_values()
{
my( $bind_server, $bind_user, $bind_pass ) = ( '', '', '' );
if( open( my $fh, '<' . $smart_nwclient_path ) )
{
my $line = <$fh>;
close( $fh );
chomp( $line ) if defined( $line );
( $bind_server, $bind_user, $bind_pass ) = split( '[/ ]', $line ) if defined( $line );
}
$bind_server = '' unless defined( $bind_server );
$bind_user = '' unless defined( $bind_user );
$bind_pass = '' unless defined( $bind_pass );
return( $bind_server, $bind_user, $bind_pass );
}
sub sync_server_hint_row( $$$$ )
{
my( $checkbox_name, $current_name, $other_name, $direction ) = @_;
$current_name = '' unless defined( $current_name );
$other_name = '' unless defined( $other_name );
my $msg = '';
if( $direction eq 'general_to_smart' )
{
$msg = 'Also update the SMArT bindery server when saving. Current SMArT bindery server: ';
$msg .= $other_name ne '' ? '' . html_escape( $other_name ) . ' ' : 'not set ';
}
else
{
$msg = 'Also update the MARS_NWE server name when saving. Current MARS_NWE server name: ';
$msg .= $other_name ne '' ? '' . html_escape( $other_name ) . ' ' : 'not set ';
}
return qq|\t\n\t\tKeep server names in sync: $msg \n\t\tYes \n\t \n|;
}
sub unix_user_import_rows()
{
my $html = '';
my @users = unix_userlist_effective();
my %existing = ();
my @candidates = ();
foreach my $line ( getconfig( 13 ) )
{
my @f = split( ' ', $line );
$existing{lc( $f[1] )} = 1 if defined( $f[1] ) && $f[1] ne '';
}
foreach my $u ( @users )
{
next if ! defined( $u->{name} ) || $u->{name} eq '';
next if defined( $u->{uid} ) && $u->{uid} < 1000;
next if lc( $u->{name} ) eq 'nobody';
next if lc( $u->{name} ) eq 'root';
next if $existing{lc( $u->{name} )};
push( @candidates, $u );
}
if( scalar( @candidates ) == 0 )
{
return qq|\t\n\t\tUnix users detected on host \n\t \n\t\n\t\tNo additional local Unix users were found for import. \n\t \n|;
}
$html .= qq|\t\n\t\tUnix users detected on host \n\t \n|;
foreach my $u ( @candidates )
{
my $name = $u->{name};
my $uid = $u->{uid};
my $gecos = defined( $u->{gecos} ) ? $u->{gecos} : '';
$gecos =~ s/,.*$//;
my $href = '/settings/users_import/' . url_escape( $name );
$html .= "\t\n";
$html .= "\t\t" . html_escape( $name ) . " UID: " . html_escape( $uid ) . " ";
$html .= " " . html_escape( $gecos ) if $gecos ne '';
$html .= " \n";
$html .= "\t\tAdd as MARS_NWE user \n";
$html .= "\t \n";
}
return $html;
}
sub unix_user_defaults_from_query()
{
my %defaults = ();
$defaults{name} = '';
$defaults{unix_user} = '';
$defaults{fullname} = '';
my $want = '';
if( defined( $p{unix_user} ) && $p{unix_user} ne '' )
{
$want = $p{unix_user};
}
elsif( defined( $c[1] ) && $c[1] eq 'users_import' && defined( $c[2] ) && $c[2] ne '' )
{
$want = $c[2];
}
elsif( defined( $c[3] ) && $c[3] ne '' )
{
$want = $c[3];
}
if( $want ne '' )
{
$want =~ s/[^-_\.A-Za-z0-9]//g;
foreach my $u ( unix_userlist_effective() )
{
next if ! defined( $u->{name} ) || $u->{name} ne $want;
my $gecos = defined( $u->{gecos} ) ? $u->{gecos} : '';
$gecos =~ s/,.*$//;
$defaults{name} = uc( $u->{name} );
$defaults{name} =~ s/[^-_\.A-Za-z0-9]//g;
$defaults{unix_user} = $u->{name};
$defaults{fullname} = $gecos;
last;
}
}
return %defaults;
}
sub selected_attr( $$ )
{
my( $a, $b ) = @_;
$a = '' unless defined( $a );
$b = '' unless defined( $b );
return $a eq $b ? ' SELECTED' : '';
}
sub smart_group_checkbox_rows( $ )
{
my $server = $_[0];
my $html = '';
my @groups = ();
my $cmd = 'nwbols -t 2 -S ' . $server;
@groups = sort( split( "\n", `$cmd` ) );
foreach my $g ( @groups )
{
my @f = split( ' ', $g );
my $group = $f[0];
next if ! defined( $group ) || $group eq '';
next if $group =~ /[^-_\.A-Za-z0-9]/;
$html .= html_escape( $group ) . ' ' . "\n";
}
if( $html eq '' )
{
$html = 'No bindery groups found. Save the user first, then add groups later. ' . "\n";
}
return $html;
}
sub smart_debug_user_import_row()
{
my $p_unix = defined( $p{unix_user} ) ? $p{unix_user} : '';
my $line = '';
$line .= 'c0=' . html_escape( defined( $c[0] ) ? $c[0] : '' ) . ' ';
$line .= 'c1=' . html_escape( defined( $c[1] ) ? $c[1] : '' ) . ' ';
$line .= 'c2=' . html_escape( defined( $c[2] ) ? $c[2] : '' ) . ' ';
$line .= 'c3=' . html_escape( defined( $c[3] ) ? $c[3] : '' ) . ' ';
$line .= 'c4=' . html_escape( defined( $c[4] ) ? $c[4] : '' ) . ' ';
$line .= 'p_unix_user=' . html_escape( $p_unix );
return qq|\t\n\t\tDebug user import: \n\t\t$line \n\t \n|;
}
sub unix_userlist_effective()
{
my @users = ();
if( defined( $smart_userlist_path ) && $smart_userlist_path ne '' && -x $smart_userlist_path )
{
my @cmd = ( $smart_userlist_path );
if( defined( $smart_userlist_pam_check ) && $smart_userlist_pam_check )
{
push( @cmd, '--pam-check' );
push( @cmd, '--pam-service', 'smart' );
}
if( open( my $fh, '-|', @cmd ) )
{
while( my $line = <$fh> )
{
chomp( $line );
my( $name, $uid, $gid, $gecos, $home, $shell ) = split( /\t/, $line, 6 );
next if ! defined( $name ) || $name eq '';
next if $name =~ /[^-_\.A-Za-z0-9]/;
push( @users, {
name => $name,
uid => $uid,
gid => $gid,
gecos => defined( $gecos ) ? $gecos : '',
home => defined( $home ) ? $home : '',
shell => defined( $shell ) ? $shell : '',
} );
}
close( $fh );
}
}
# Last-resort compatibility fallback: old SMArT unix_userlist() implementation.
if( scalar( @users ) == 0 )
{
@users = unix_userlist();
}
return @users;
}
sub handle_request()
{
if( $c[1] eq 'general' )
{
$server_name = getconfigline( 2 );
$internal_net = getconfigline( 3 );
my( $smart_bind_server_for_general, $smart_bind_user_for_general, $smart_bind_pass_for_general ) = smart_bindery_values();
my $general_sync_server_row = sync_server_hint_row( 'sync_smart_bind_server', $server_name, $smart_bind_server_for_general, 'general_to_smart' );
( $burst_read, $burst_write ) = split( ' ', getconfigline( 30 ) );
$timing_down = getconfigline( 210 );
$timing_warn = getconfigline( 211 );
eval( '$test_' . getconfigline( 16 ) . ' = " CHECKED";' );
eval( '$version_' . ( split( ' ', getconfigline( 6 ) ) )[0] . ' = " CHECKED";' );
if( ( split( ' ', getconfigline( 6 ) ) )[1] == 1 )
{
$burst_enabled_0 = ' CHECKED';
}
else
{
$burst_enabled_0 = '';
}
print <
SMArT Settings
$settings_nav_bar