From 58a2e8da04ad4bc555f72152f977003e4d1854d6 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Fri, 22 May 2026 07:32:17 +0200 Subject: [PATCH] Big collection --- apply.pl | 849 +++++++++++++++++++++++++++++++++++++++- settings.pl | 521 ++++++++++++++++++++++-- smart.cmake | 4 +- static/start.html.cmake | 43 +- 4 files changed, 1361 insertions(+), 56 deletions(-) diff --git a/apply.pl b/apply.pl index 4eeb4d7..1bf4075 100644 --- a/apply.pl +++ b/apply.pl @@ -59,6 +59,774 @@ sub write_smart_bindery_values( $$$ ) } +sub mars_nwe_service_active() +{ + my $service = defined( $mars_nwe_service ) && $mars_nwe_service ne '' ? $mars_nwe_service : 'mars-nwe-serv.service'; + my $systemctl = defined( $smart_systemctl_path ) && $smart_systemctl_path ne '' ? $smart_systemctl_path : '/usr/bin/systemctl'; + + return 0 if ! -x $systemctl; + + my $rc = system( $systemctl, 'is-active', '--quiet', $service ); + + return $rc == 0 ? 1 : 0; +} + +sub service_required_page( $ ) +{ + my $what = $_[0]; + $what = 'this operation' unless defined( $what ) && $what ne ''; + + print < + +SMArT Service required + + + + +
+
MARS_NWE service is not running
+
+

Cannot manage $what while mars-nwe-serv.service is stopped.

+

Start the MARS_NWE service first, then retry the operation.

+
+ +
+ + +EOF +} + +sub require_mars_nwe_service( $ ) +{ + my $what = $_[0]; + + if( ! mars_nwe_service_active() ) + { + service_required_page( $what ); + return 0; + } + + return 1; +} + + +sub apply_html_escape( $ ) +{ + my $s = $_[0]; + + $s = '' unless defined( $s ); + $s =~ s/&/&/g; + $s =~ s//>/g; + $s =~ s/"/"/g; + + return $s; +} + + +sub validation_error_page( $$ ) +{ + my( $title, $msg ) = @_; + + $title = 'Validation error' unless defined( $title ) && $title ne ''; + $msg = 'The submitted values are not valid.' unless defined( $msg ) && $msg ne ''; + + my @detail_rows = (); + + if( $title =~ /User/i ) + { + push( @detail_rows, [ 'User name', defined( $p{name} ) ? $p{name} : ( defined( $c[2] ) ? $c[2] : '' ) ] ); + push( @detail_rows, [ 'Full name', defined( $p{fullname} ) ? $p{fullname} : '' ] ); + push( @detail_rows, [ 'Unix user', defined( $p{unix_user} ) ? $p{unix_user} : '' ] ); + push( @detail_rows, [ 'Password', defined( $p{password} ) && $p{password} ne '' ? '(set)' : '(unchanged)' ] ); + } + elsif( $title =~ /Group/i ) + { + push( @detail_rows, [ 'Group name', defined( $p{name} ) ? $p{name} : ( defined( $c[2] ) ? $c[2] : '' ) ] ); + push( @detail_rows, [ 'Full name', defined( $p{fullname} ) ? $p{fullname} : '' ] ); + } + else + { + if( defined( $p{name} ) || defined( $p{path} ) ) + { + push( @detail_rows, [ 'Volume name', defined( $p{name} ) ? $p{name} : '' ] ) if defined( $p{path} ); + push( @detail_rows, [ 'Unix path', defined( $p{path} ) ? $p{path} : '' ] ) if defined( $p{path} ); + } + + if( defined( $p{number} ) || defined( $p{network} ) || defined( $p{interface} ) || defined( $p{frametype} ) || defined( $p{frame_type} ) || defined( $p{delay} ) ) + { + push( @detail_rows, [ 'Network number', defined( $p{number} ) ? $p{number} : ( defined( $p{network} ) ? $p{network} : '' ) ] ); + push( @detail_rows, [ 'Network interface', defined( $p{interface} ) ? $p{interface} : '' ] ); + push( @detail_rows, [ 'Frame type', defined( $p{frametype} ) ? $p{frametype} : ( defined( $p{frame_type} ) ? $p{frame_type} : '' ) ] ); + push( @detail_rows, [ 'Interface delay', defined( $p{delay} ) ? $p{delay} : '' ] ); + } + + if( defined( $p{command} ) || defined( $p{spool} ) || defined( $p{unix_print} ) || defined( $p{spool_dir} ) ) + { + push( @detail_rows, [ 'Queue name', defined( $p{name} ) ? $p{name} : ( defined( $c[2] ) ? $c[2] : '' ) ] ); + push( @detail_rows, [ 'Unix printing command', defined( $p{command} ) ? $p{command} : ( defined( $p{unix_print} ) ? $p{unix_print} : '' ) ] ); + push( @detail_rows, [ 'Spool directory', defined( $p{spool} ) ? $p{spool} : ( defined( $p{spool_dir} ) ? $p{spool_dir} : '' ) ] ); + } + } + + my $details_html = ''; + + foreach my $row ( @detail_rows ) + { + my $key = apply_html_escape( $row->[0] ); + my $val = apply_html_escape( $row->[1] ); + + $details_html .= qq|
$key
$val
\n|; + } + + $title = apply_html_escape( $title ); + $msg = apply_html_escape( $msg ); + + print < + +SMArT validation error + + + + +
+
$title
+
+
$msg
+
+$details_html
+
+
+ Back +
+
+ + +EOF +} + +sub normalize_volume_name( $ ) +{ + my $name = $_[0]; + + $name = '' unless defined( $name ); + $name =~ s/^\s+//; + $name =~ s/\s+$//; + $name = uc( $name ); + $name =~ s/[^A-Z0-9_\-]/_/g; + $name =~ s/_+/_/g; + $name = substr( $name, 0, 15 ); + + return $name; +} + +sub validate_volume_params() +{ + $p{name} = normalize_volume_name( $p{name} ); + + $p{path} = '' unless defined( $p{path} ); + $p{path} =~ s/^\s+//; + $p{path} =~ s/\s+$//; + $p{path} =~ s/[\r\n\t']//g; + + if( $p{name} eq '' ) + { + validation_error_page( 'Volume validation error', 'Volume name must not be empty.' ); + return 0; + } + + if( $p{path} eq '' ) + { + validation_error_page( 'Volume validation error', 'Unix path must not be empty.' ); + return 0; + } + + if( $p{path} !~ m#^/# ) + { + validation_error_page( 'Volume validation error', 'Unix path must be an absolute path starting with /.' ); + return 0; + } + + return 1; +} + + +sub normalize_device_network( $ ) +{ + my $number = $_[0]; + + $number = '' unless defined( $number ); + $number =~ s/^\s+//; + $number =~ s/\s+$//; + $number = uc( $number ); + $number =~ s/^0X//; + $number =~ s/[^0-9A-F]//g; + + return $number; +} + +sub normalize_device_frame_type( $ ) +{ + my $frame = $_[0]; + + $frame = '' unless defined( $frame ); + $frame =~ s/^\s+//; + $frame =~ s/\s+$//; + + return '802.2' if $frame eq '8022'; + return '802.3' if $frame eq '8023'; + return 'Ethernet_II' if lc( $frame ) eq 'ethernet_ii' || lc( $frame ) eq 'ethernetii'; + return 'SNAP' if uc( $frame ) eq 'SNAP'; + + return $frame; +} + +sub validate_device_params() +{ + $p{number} = $p{network} if ! defined( $p{number} ) && defined( $p{network} ); + $p{frametype} = $p{frame_type} if ! defined( $p{frametype} ) && defined( $p{frame_type} ); + + $p{number} = normalize_device_network( $p{number} ); + + $p{interface} = '' unless defined( $p{interface} ); + $p{interface} =~ s/^\s+//; + $p{interface} =~ s/\s+$//; + $p{interface} =~ s/[\r\n\t']//g; + + $p{frametype} = normalize_device_frame_type( $p{frametype} ); + + $p{delay} = '' unless defined( $p{delay} ); + $p{delay} =~ s/^\s+//; + $p{delay} =~ s/\s+$//; + + if( $p{number} eq '' ) + { + validation_error_page( 'Device validation error', 'Network number must not be empty.' ); + return 0; + } + + if( $p{number} !~ /^[0-9A-F]+$/ ) + { + validation_error_page( 'Device validation error', 'Network number must be hexadecimal.' ); + return 0; + } + + if( length( $p{number} ) > 8 ) + { + validation_error_page( 'Device validation error', 'Network number must not be longer than 8 hex digits.' ); + return 0; + } + + if( $p{interface} eq '' ) + { + validation_error_page( 'Device validation error', 'Network interface must not be empty.' ); + return 0; + } + + if( $p{interface} !~ /^[-_.:A-Za-z0-9]+$/ ) + { + validation_error_page( 'Device validation error', 'Network interface contains invalid characters.' ); + return 0; + } + + if( $p{frametype} !~ /^(802\.2|802\.3|Ethernet_II|SNAP)$/ ) + { + validation_error_page( 'Device validation error', 'Frame type must be one of: 802.2, 802.3, Ethernet_II, SNAP.' ); + return 0; + } + + if( $p{delay} eq '' ) + { + $p{delay} = 1; + } + + if( $p{delay} !~ /^[0-9]+$/ ) + { + validation_error_page( 'Device validation error', 'Interface delay must be a number.' ); + return 0; + } + + return 1; +} + + +sub normalize_queue_name( $ ) +{ + my $name = $_[0]; + + $name = '' unless defined( $name ); + $name =~ s/^\s+//; + $name =~ s/\s+$//; + $name = uc( $name ); + $name =~ s/[^A-Z0-9_\-]/_/g; + $name =~ s/_+/_/g; + $name = substr( $name, 0, 47 ); + + return $name; +} + +sub validate_queue_params() +{ + # Real form field names are unix_print and spool_dir. Keep command/spool + # aliases for compatibility with older or hand-crafted URLs. + $p{command} = $p{unix_print} if ! defined( $p{command} ) && defined( $p{unix_print} ); + $p{spool} = $p{spool_dir} if ! defined( $p{spool} ) && defined( $p{spool_dir} ); + + # Existing queue edit forms do not send a name field. In that case the + # queue name is the URL component: /apply/queues/. + if( ( ! defined( $p{name} ) || $p{name} eq '' ) && defined( $c[2] ) && $c[2] ne '' && $c[2] ne 'add_new' ) + { + $p{name} = $c[2]; + } + + $p{name} = normalize_queue_name( $p{name} ); + + $p{command} = '' unless defined( $p{command} ); + $p{command} =~ s/^\s+//; + $p{command} =~ s/\s+$//; + $p{command} =~ s/[\r\n\t']//g; + $p{unix_print} = $p{command}; + + $p{spool} = '' unless defined( $p{spool} ); + $p{spool} =~ s/^\s+//; + $p{spool} =~ s/\s+$//; + $p{spool} =~ s/[\r\n\t']//g; + $p{spool} =~ s#/{2,}#/#g; + $p{spool_dir} = $p{spool}; + + if( $p{name} eq '' ) + { + validation_error_page( 'Print queue validation error', 'Queue name must not be empty.' ); + return 0; + } + + if( $p{command} eq '' ) + { + validation_error_page( 'Print queue validation error', 'Unix printing command must not be empty.' ); + return 0; + } + + # The spool directory for MARS_NWE print queues may be a NetWare volume + # path such as SYS:SYSTEM/0003002.QDR. It is not necessarily a Unix + # absolute path. Empty is also allowed because MARS_NWE can generate/fill + # this value for queues. + if( $p{spool} ne '' ) + { + if( $p{spool} !~ m#^/# && $p{spool} !~ /^[A-Za-z0-9_\-]+:[A-Za-z0-9_\-\.\/]+$/ ) + { + validation_error_page( 'Print queue validation error', 'Spool directory must be empty, a Unix absolute path, or a NetWare path like SYS:SYSTEM/0003002.QDR.' ); + return 0; + } + } + + return 1; +} + + +sub normalize_bindery_name( $ ) +{ + my $name = $_[0]; + + $name = '' unless defined( $name ); + $name =~ s/^\s+//; + $name =~ s/\s+$//; + $name = uc( $name ); + $name = substr( $name, 0, 47 ); + + return $name; +} + +sub bindery_name_has_valid_chars( $ ) +{ + my $name = $_[0]; + + $name = '' unless defined( $name ); + + return $name =~ /^[A-Z0-9_\-]+$/ ? 1 : 0; +} + + +sub clean_bindery_text( $ ) +{ + my $text = $_[0]; + + $text = '' unless defined( $text ); + $text =~ s/[\r\n\t']//g; + $text =~ s/^\s+//; + $text =~ s/\s+$//; + $text = substr( $text, 0, 255 ); + + return $text; +} + +sub validate_user_params() +{ + # Existing user edit forms do not have to provide a name field. + if( ( ! defined( $p{name} ) || $p{name} eq '' ) && defined( $c[2] ) && $c[2] ne '' && $c[2] ne 'add_new' ) + { + $p{name} = $c[2]; + } + + $p{name} = normalize_bindery_name( $p{name} ); + $p{fullname} = clean_bindery_text( $p{fullname} ); + + $p{unix_user} = '' unless defined( $p{unix_user} ); + $p{unix_user} =~ s/^\s+//; + $p{unix_user} =~ s/\s+$//; + $p{unix_user} =~ s/[\r\n\t']//g; + + $p{password} = '' unless defined( $p{password} ); + + if( $p{name} eq '' ) + { + validation_error_page( 'User validation error', 'User name must not be empty.' ); + return 0; + } + + if( ! bindery_name_has_valid_chars( $p{name} ) ) + { + validation_error_page( 'User validation error', 'User name contains invalid characters. Allowed characters are A-Z, 0-9, _ and -.' ); + return 0; + } + + if( $p{unix_user} ne '' && $p{unix_user} !~ /^[-_.A-Za-z0-9]+$/ ) + { + validation_error_page( 'User validation error', 'Unix user contains invalid characters.' ); + return 0; + } + + if( $p{password} =~ /[\r\n]/ ) + { + validation_error_page( 'User validation error', 'Password must not contain line breaks.' ); + return 0; + } + + return 1; +} + +sub validate_group_params() +{ + # Existing group edit forms do not have to provide a name field. + if( ( ! defined( $p{name} ) || $p{name} eq '' ) && defined( $c[2] ) && $c[2] ne '' && $c[2] ne 'add_new' ) + { + $p{name} = $c[2]; + } + + $p{name} = normalize_bindery_name( $p{name} ); + $p{fullname} = clean_bindery_text( $p{fullname} ); + + if( $p{name} eq '' ) + { + validation_error_page( 'Group validation error', 'Group name must not be empty.' ); + return 0; + } + + if( ! bindery_name_has_valid_chars( $p{name} ) ) + { + validation_error_page( 'Group validation error', 'Group name contains invalid characters. Allowed characters are A-Z, 0-9, _ and -.' ); + return 0; + } + + return 1; +} + + +sub apply_url_escape( $ ) +{ + my $s = $_[0]; + + $s = '' unless defined( $s ); + $s =~ s/([^A-Za-z0-9_\-\. ])/sprintf( "%%%02X", ord( $1 ) )/eg; + $s =~ s/ /+/g; + + return $s; +} + +sub redirect_msg( $$ ) +{ + my( $url, $msg ) = @_; + + $msg = apply_url_escape( $msg ); + + if( $url =~ /\?/ ) + { + redirect( $url . '&msg=' . $msg ); + } + else + { + redirect( $url . '?msg=' . $msg ); + } +} + + +sub apply_log_line( $$ ) +{ + my( $level, $msg ) = @_; + + $level = 'INFO' unless defined( $level ) && $level ne ''; + $msg = '' unless defined( $msg ); + + my( $sec, $min, $hour, $mday, $mon, $year ) = localtime( time() ); + my $ts = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", + $year + 1900, $mon + 1, $mday, $hour, $min, $sec ); + + my $version = defined( $smart_version ) && $smart_version ne '' ? $smart_version : '0.99.pl28'; + my $line = '[' . $ts . '] [' . $level . '] [SMArT ' . $version . '] [apply.pl] ' . $msg . "\n"; + + if( defined( $bindery_log_fh ) ) + { + print( $bindery_log_fh $line ); + return; + } + + if( defined( $smart_log_path ) && $smart_log_path ne '' && open( my $fh, '>>', $smart_log_path ) ) + { + print( $fh $line ); + close( $fh ); + return; + } + + print STDERR '[apply.pl] ' . $msg . "\n"; +} + +sub bindery_error_page( $$$ ) +{ + my( $title, $cmd, $output ) = @_; + + $title = 'Bindery command failed' unless defined( $title ) && $title ne ''; + $cmd = '' unless defined( $cmd ); + $output = '' unless defined( $output ); + + $title = apply_html_escape( $title ); + $cmd = apply_html_escape( $cmd ); + $output = apply_html_escape( $output ); + + print < + +SMArT bindery command failed + + + + +
+
$title
+
+
A MARS_NWE bindery command returned an error. The command output was also written to the SMArT log.
+
+
Command
$cmd
+
Output
$output
+
+
+ +
+ + +EOF +} + +sub apply_shell_quote( $ ) +{ + my $s = $_[0]; + + $s = '' unless defined( $s ); + $s =~ s/'/'"'"'/g; + + return "'" . $s . "'"; +} + + +our $bindery_log_fh; +our $bindery_cmd_ok_count = 0; + +sub open_bindery_command_log() +{ + return 1 if defined( $bindery_log_fh ); + + return 0 if ! defined( $smart_log_path ) || $smart_log_path eq ''; + + if( open( $bindery_log_fh, '>>', $smart_log_path ) ) + { + select( ( select( $bindery_log_fh ), $| = 1 )[0] ); + apply_log_line( 'INFO', 'bindery command log handle opened before drop_root' ); + return 1; + } + + print STDERR '[apply.pl] could not pre-open bindery command log: ' . $! . "\n"; + return 0; +} + + +sub bindery_success_page( $$$ ) +{ + my( $title, $message, $back_url ) = @_; + + $title = 'Bindery operation completed' unless defined( $title ) && $title ne ''; + $message = 'The bindery operation completed successfully.' unless defined( $message ) && $message ne ''; + $back_url = '/settings' unless defined( $back_url ) && $back_url ne ''; + + my $count = defined( $bindery_cmd_ok_count ) ? $bindery_cmd_ok_count : 0; + + $title = apply_html_escape( $title ); + $message = apply_html_escape( $message ); + $back_url = apply_html_escape( $back_url ); + + print < + +SMArT bindery operation completed + + + + +
+
$title
+
+
$message
+
+
Bindery commands
$count completed successfully
+
Log
Details were written to the SMArT log.
+
+
+ +
+ + +EOF +} + + +sub run_bindery_cmd( @ ) +{ + my @cmd = @_; + my $cmdline = join( ' ', map { apply_shell_quote( $_ ) } @cmd ); + + apply_log_line( 'INFO', 'bindery command: ' . $cmdline ); + + my $output = `$cmdline 2>&1`; + my $rc = $?; + + $output = '' unless defined( $output ); + $output =~ s/\r//g; + + if( $rc != 0 ) + { + my $exit = $rc >> 8; + apply_log_line( 'ERROR', 'bindery command failed rc=' . $exit . ' cmd=' . $cmdline . ' output=' . $output ); + bindery_error_page( 'Bindery command failed', $cmdline, $output ); + return 0; + } + + $bindery_cmd_ok_count++; + apply_log_line( 'INFO', 'bindery command ok cmd=' . $cmdline . ( $output ne '' ? ' output=' . $output : '' ) ); + + return 1; +} + +sub run_shell_bindery_cmd( $ ) +{ + my $cmd = $_[0]; + $cmd = '' unless defined( $cmd ); + + apply_log_line( 'INFO', 'bindery shell command: ' . $cmd ); + + my $output = `$cmd 2>&1`; + my $rc = $?; + + $output = '' unless defined( $output ); + $output =~ s/\r//g; + + if( $rc != 0 ) + { + my $exit = $rc >> 8; + apply_log_line( 'ERROR', 'bindery shell command failed rc=' . $exit . ' cmd=' . $cmd . ' output=' . $output ); + bindery_error_page( 'Bindery command failed', $cmd, $output ); + return 0; + } + + apply_log_line( 'INFO', 'bindery shell command ok cmd=' . $cmd . ( $output ne '' ? ' output=' . $output : '' ) ); + + return 1; +} + + sub handle_request() { if( $c[1] eq 'general' ) @@ -128,6 +896,10 @@ sub handle_request() } elsif( $c[1] eq 'volumes' ) { + if( defined( $p{name} ) || defined( $p{path} ) ) + { + return( 1 ) if ! validate_volume_params(); + } if( $c[2] ne '' ) { delconfigline( '1 ' . $c[2] ); @@ -143,10 +915,14 @@ sub handle_request() cbc( 'N' ); addconfigline( '1 ' . $p{name} . ' ' . $p{path} . ' ' . $p{case} . $p{trustee} . $p{m} . $p{n} . $p{o} . $p{p} . $p{r} . $p{O} . $p{N} ); } - redirect( '/settings/volumes' ); + redirect_msg( '/settings/volumes', 'Volume settings saved.' ); } elsif( $c[1] eq 'devices' ) { + if( defined( $p{number} ) || defined( $p{network} ) || defined( $p{interface} ) || defined( $p{frametype} ) || defined( $p{frame_type} ) || defined( $p{delay} ) ) + { + return( 1 ) if ! validate_device_params(); + } if( $c[2] ne '' ) { delconfigline( '4 ' . $c[2] ); @@ -156,7 +932,7 @@ sub handle_request() { addconfigline( '4 ' . $p{number} . ' ' . $p{interface} . ' ' . $p{frametype} . ' ' . $p{delay} ); } - redirect( '/settings/devices' ); + redirect_msg( '/settings/devices', 'Device settings saved.' ); } elsif( $c[1] eq 'logging' ) { @@ -196,20 +972,38 @@ sub handle_request() } elsif( $c[1] eq 'users' ) { + my $should_update_unix_user = 0; + $should_update_unix_user = 1 if defined( $c[2] ) && $c[2] eq 'add_new'; + $should_update_unix_user = 1 if defined( $p{change_unix_user} ) && $p{change_unix_user} eq '1'; + apply_log_line( 'INFO', 'UNIX_USER update requested: ' . ( $should_update_unix_user ? 'yes' : 'no' ) ); + + if( scalar( keys( %p ) ) > 0 ) + { + return( 1 ) if ! validate_user_params(); + } + return( 1 ) if ! require_mars_nwe_service( 'users' ); + open_bindery_command_log(); drop_root(); $server = get_server(); if( $c[2] eq 'add_new' ) { - system( 'nwbocreate', '-S', $server, '-t', '1', '-o', $p{name} ); + return( 1 ) if ! run_bindery_cmd( 'nwbocreate', '-S', $server, '-t', '1', '-o', $p{name} ); $c[2] = $p{name}; } else { - system( 'nwbprm', '-S', $server, '-t', '1', '-o', $c[2], '-p', 'UNIX_USER' ); - system( 'nwbprm', '-S', $server, '-t', '1', '-o', $c[2], '-p', 'IDENTIFICATION' ); - system( 'nwbprm', '-S', $server, '-t', '1', '-o', $c[2], '-p', 'GROUPS_I\'M_IN' ); - system( 'nwbprm', '-S', $server, '-t', '1', '-o', $c[2], '-p', 'SECURITY_EQUALS' ); + if( $should_update_unix_user ) + { + return( 1 ) if ! run_bindery_cmd( 'nwbprm', '-S', $server, '-t', '1', '-o', $c[2], '-p', 'UNIX_USER' ); + } + else + { + apply_log_line( 'INFO', 'UNIX_USER removal skipped for existing user ' . $c[2] ); + } +return( 1 ) if ! run_bindery_cmd( 'nwbprm', '-S', $server, '-t', '1', '-o', $c[2], '-p', 'IDENTIFICATION' ); + return( 1 ) if ! run_bindery_cmd( 'nwbprm', '-S', $server, '-t', '1', '-o', $c[2], '-p', 'GROUPS_I\'M_IN' ); + return( 1 ) if ! run_bindery_cmd( 'nwbprm', '-S', $server, '-t', '1', '-o', $c[2], '-p', 'SECURITY_EQUALS' ); } $id = ( split( ' ', ( grep( /^$c[2] /, split( "\n", `nwbols -S $server` ) ) )[0] ) )[1]; # Woohoo... ;) @@ -219,11 +1013,14 @@ sub handle_request() if( scalar( keys( %p ) ) == 0 ) { - system( 'nwborm', '-S', $server, '-t', '1', '-o', $c[2] ); + return( 1 ) if ! run_bindery_cmd( 'nwborm', '-S', $server, '-t', '1', '-o', $c[2] ); } else { - write_property_string( $c[2], 1, 'UNIX_USER', 0, 30, $p{unix_user} ); + if( $should_update_unix_user ) + { + write_property_string( $c[2], 1, 'UNIX_USER', 0, 30, $p{unix_user} ); + } write_property_string( $c[2], 1, 'IDENTIFICATION', 0, 30, $p{fullname} ); open( FILE, '|' . 'nwbpset -S ' . $server ); @@ -270,17 +1067,23 @@ EOF } } - redirect( '/settings/users' ); + bindery_success_page( 'User saved', 'User bindery settings were saved successfully.', '/settings/users' ); return( 1 ); } elsif( $c[1] eq 'groups' ) { + if( scalar( keys( %p ) ) > 0 ) + { + return( 1 ) if ! validate_group_params(); + } + return( 1 ) if ! require_mars_nwe_service( 'groups' ); + open_bindery_command_log(); drop_root(); $server = get_server(); if( $c[2] eq 'add_new' ) { - system( 'nwbocreate', '-S', $server, '-t', '2', '-o', $p{name} ); + return( 1 ) if ! run_bindery_cmd( 'nwbocreate', '-S', $server, '-t', '2', '-o', $p{name} ); open( FILE, '|' . 'nwbpset -S ' . $server ); print FILE < $mountpoint, + fstype => $fstype ne '' ? $fstype : 'mount', + source => $source ne '' ? $source : 'mount', + name => volume_name_from_path( $mountpoint ), + } ); + + $seen{$mountpoint} = 1; + }; + + foreach my $file ( '/proc/1/mountinfo', '/proc/self/mountinfo' ) + { + next if ! open( my $fh, '<', $file ); + + while( my $line = <$fh> ) + { + chomp( $line ); + + my @parts = split( / - /, $line, 2 ); + next if scalar( @parts ) != 2; + + my @left = split( /\s+/, $parts[0] ); + my @right = split( /\s+/, $parts[1] ); + + next if scalar( @left ) < 5; + next if scalar( @right ) < 3; + + $add_mount->( $left[4], $right[0], $right[1] ); + } + + close( $fh ); + } + + if( open( my $fh, '<', '/proc/mounts' ) ) + { + while( my $line = <$fh> ) + { + chomp( $line ); + + my( $source, $mountpoint, $fstype ) = split( /\s+/, $line ); + $add_mount->( $mountpoint, $fstype, $source ); + } + + close( $fh ); + } + + @mounts = sort { $a->{path} cmp $b->{path} } @mounts; + + return @mounts; +} + +sub volume_mount_import_rows() +{ + my $html = ''; + my @mounts = local_mountpoints(); + my %existing = (); + + foreach my $line ( getconfig( 1 ) ) + { + my @f = split( ' ', $line ); + $existing{lc( $f[1] )} = 1 if defined( $f[1] ) && $f[1] ne ''; + $existing{'path:' . $f[2]} = 1 if defined( $f[2] ) && $f[2] ne ''; + } + + if( scalar( @mounts ) == 0 ) + { + return qq|\t\n\t\tLocal mountpoints detected on host\n\t\n\t\n\t\tNo suitable local mountpoints were found.\n\t\n|; + } + + $html .= qq|\t\n\t\tLocal mountpoints detected on host\n\t\n|; + + foreach my $m ( @mounts ) + { + my $name = $m->{name}; + my $path = $m->{path}; + my $fstype = $m->{fstype}; + my $source = $m->{source}; + my $exists = $existing{lc( $name )} || $existing{'path:' . $path}; + + $html .= "\t\n"; + $html .= "\t\t" . html_escape( $path ) . "
Volume: " . html_escape( $name ) . "   Type: " . html_escape( $fstype ) . "   Source: " . html_escape( $source ) . "\n"; + + if( $exists ) + { + $html .= "\t\talready configured\n"; + } + else + { + my $href = '/settings/volumes/add_new?mount_path=' . url_escape( $path ) . '&path=' . url_escape( $path ); + $html .= "\t\tAdd as volume\n"; + } + + $html .= "\t\n"; + } + + return $html; +} + +sub volume_mount_defaults_from_query() +{ + my %defaults = (); + + $defaults{name} = ''; + $defaults{path} = ''; + + my $path = ''; + + if( defined( $p{mount_path} ) && $p{mount_path} ne '' ) + { + $path = $p{mount_path}; + } + elsif( defined( $p{path} ) && $p{path} ne '' ) + { + $path = $p{path}; + } + + if( $path ne '' ) + { + $path =~ s/[\r\n\t']//g; + $path =~ s#/{2,}#/#g; + $path =~ s#/$## if length( $path ) > 1; + + if( volume_mountpoint_is_useful( $path, 'mount' ) ) + { + $defaults{path} = $path; + $defaults{name} = volume_name_from_path( $path ); + } + } + + return %defaults; +} + + +sub mars_nwe_service_active() +{ + my $service = defined( $mars_nwe_service ) && $mars_nwe_service ne '' ? $mars_nwe_service : 'mars-nwe-serv.service'; + my $systemctl = defined( $smart_systemctl_path ) && $smart_systemctl_path ne '' ? $smart_systemctl_path : '/usr/bin/systemctl'; + + return 0 if ! -x $systemctl; + + my $rc = system( $systemctl, 'is-active', '--quiet', $service ); + + return $rc == 0 ? 1 : 0; +} + +sub mars_nwe_service_guard_rows( $ ) +{ + my $what = $_[0]; + $what = 'this section' unless defined( $what ) && $what ne ''; + + return '' if mars_nwe_service_active(); + + return qq|\t\n| + . qq|\t\tMARS_NWE service is not running.
\n| + . qq|\t\tStart mars-nwe-serv.service before managing | . html_escape( $what ) . qq|. | + . qq|Open service page\n| + . qq|\t\n|; +} + + +sub settings_message_html() +{ + return '' if ! defined( $p{msg} ) || $p{msg} eq ''; + + my $msg = $p{msg}; + $msg =~ s/\+/ /g; + + return qq|
| . html_escape( $msg ) . qq|
\n|; +} + + +sub user_group_checkbox_rows( $ ) +{ + my $user = $_[0]; + my $html = ''; + my %selected = (); + + $user = '' unless defined( $user ); + + if( $user ne '' && $user ne 'add_new' ) + { + my $server = get_server(); + my $groups = `nwbpvalues -S $server -t 1 -o $user -p "GROUPS_I'M_IN" 2>/dev/null`; + + foreach my $line ( split( /\n/, $groups ) ) + { + if( $line =~ /([A-Za-z0-9_\-]+)/ ) + { + $selected{uc( $1 )} = 1; + } + } + } + + my @groups = getconfig( 12 ); + my @names = (); + + foreach my $g ( @groups ) + { + my @f = split( /\s+/, $g ); + next if ! defined( $f[1] ) || $f[1] eq ''; + push( @names, uc( $f[1] ) ); + } + + push( @names, 'EVERYONE' ) if ! grep { $_ eq 'EVERYONE' } @names; + @names = sort @names; + + foreach my $g ( @names ) + { + my $checked = ( $selected{$g} || ( $g eq 'EVERYONE' && $user eq 'add_new' ) ) ? ' CHECKED' : ''; + $html .= qq|\n|; + } + + return $html; +} + + +sub current_bindery_unix_user( $ ) +{ + # Do not query the live bindery while rendering the edit form. + # Some nwbpvalues versions can block or print unexpected data here. + # Existing UNIX_USER stays unchanged unless the admin selects a value. + return ''; +} + +sub smart_unix_users_for_select() +{ + my @users = (); + my %seen = (); + + my $helper = '/usr/lib64/mars_nwe/smart_userlist'; + + if( -x $helper && open( my $fh, '-|', $helper ) ) + { + while( my $line = <$fh> ) + { + chomp( $line ); + + my( $name, $uid, $gid, $gecos, $home, $shell ) = split( /\t/, $line ); + + next if ! defined( $name ) || $name eq ''; + next if $seen{$name}; + + push( @users, { + name => $name, + uid => defined( $uid ) ? $uid : '', + fullname => defined( $gecos ) ? $gecos : '', + } ); + + $seen{$name} = 1; + } + + close( $fh ); + } + + # Fallback: getent passwd. This is only used when smart_userlist is not + # available or returned nothing. + if( scalar( @users ) == 0 && open( my $fh, '-|', 'getent', 'passwd' ) ) + { + while( my $line = <$fh> ) + { + chomp( $line ); + + my( $name, undef, $uid, $gid, $gecos, $home, $shell ) = split( /:/, $line ); + + next if ! defined( $name ) || $name eq ''; + next if defined( $uid ) && $uid ne '' && $uid < 1000; + next if defined( $shell ) && $shell =~ m#/(false|nologin)$#; + next if $seen{$name}; + + push( @users, { + name => $name, + uid => defined( $uid ) ? $uid : '', + fullname => defined( $gecos ) ? $gecos : '', + } ); + + $seen{$name} = 1; + } + + close( $fh ); + } + + @users = sort { $a->{name} cmp $b->{name} } @users; + + return @users; +} + + +sub unix_user_select_options( $ ) +{ + my $selected = $_[0]; + my $html = ''; + + $selected = '' unless defined( $selected ); + + foreach my $u ( smart_unix_users_for_select() ) + { + next if ! defined( $u ) || ref( $u ) ne 'HASH'; + + my $name = $u->{name}; + next if ! defined( $name ) || $name eq ''; + + my $sel = $name eq $selected ? ' SELECTED' : ''; + $html .= ' +"; + } + + if( $selected ne '' && $html !~ /VALUE="\Q$selected\E"/ ) + { + $html = '\n" . $html; + } + + return $html; +} + + +sub user_unix_mapping_row( $$ ) +{ + my( $user, $selected ) = @_; + + $user = '' unless defined( $user ); + $selected = '' unless defined( $selected ); + + my $unix_user_options = unix_user_select_options( $selected ); + + if( $user eq 'add_new' ) + { + return qq| \n| + . qq|\t\tUNIX user:\n| + . qq|\t\t\n| + . qq|\t\t\t
\n| + . qq|\t\t\n| + . qq|\t\n|; + } + + my $display = $selected ne '' ? $selected : '(unchanged)'; + + return qq| \n| + . qq|\t\tUNIX user:\n| + . qq|\t\t| . html_escape( $display ) . qq|
\n| + . qq|\t\t\t\n| + . qq|\t\t\t\n| + . qq|\t\t\n| + . qq|\t\n|; +} + + sub handle_request() { if( $c[1] eq 'general' ) @@ -849,12 +1266,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar -
+@{[ settings_message_html() ]}
@@ -1008,12 +1426,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
@@ -1137,12 +1556,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
@@ -1271,12 +1691,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
@@ -1394,12 +1815,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
@@ -1561,12 +1983,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
@@ -1768,6 +2191,7 @@ EOF } elsif( $c[1] eq 'volumes' ) { + my $volume_mount_import_rows = volume_mount_import_rows(); if( $c[2] eq '' ) { print < $settings_nav_bar - +@{[ settings_message_html() ]}
@@ -1848,9 +2274,36 @@ EOF } else { - $c = getconfigline( '1 ' . $c[2] ); + my $volume_action = $c[2]; + + $c = getconfigline( '1 ' . $volume_action ); @c = split( ' ', $c ); $c = ''; + my %volume_mount_defaults = volume_mount_defaults_from_query(); + + if( $volume_action eq 'add_new' ) + { + $c[0] = $volume_mount_defaults{name} if defined( $volume_mount_defaults{name} ) && $volume_mount_defaults{name} ne ''; + $c[1] = $volume_mount_defaults{path} if defined( $volume_mount_defaults{path} ) && $volume_mount_defaults{path} ne ''; + $c[2] = 'k' if ! defined( $c[2] ) || $c[2] eq ''; + } + if( $volume_action eq 'add_new' ) + { + my %volume_mount_defaults = volume_mount_defaults_from_query(); + + if( defined( $volume_mount_defaults{name} ) && $volume_mount_defaults{name} ne '' ) + { + $c[0] = $volume_mount_defaults{name}; + } + + if( defined( $volume_mount_defaults{path} ) && $volume_mount_defaults{path} ne '' ) + { + $c[1] = $volume_mount_defaults{path}; + } + + # Default new imported volumes to lower-case filenames. + $c[2] = 'k' if ! defined( $c[2] ) || $c[2] eq ''; + } $d = $c[2]; $d =~ s/[^ik]//g; @@ -1928,12 +2381,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
Volumes @@ -1836,6 +2261,7 @@ EOF print < + $volume_mount_import_rows Add new volume
@@ -2100,12 +2554,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
Devices @@ -2216,12 +2671,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
@@ -2327,12 +2783,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
@@ -2441,12 +2898,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
Users @@ -2553,6 +3011,9 @@ EOF } } + my $user_group_checkbox_rows = user_group_checkbox_rows( $c[2] ); + my $current_unix_user = defined( $p{unix_user} ) ? $p{unix_user} : ''; + my $user_unix_mapping_row = user_unix_mapping_row( $c[2], $current_unix_user ); print < $settings_nav_bar +@{[ settings_message_html() ]} @@ -2635,21 +3098,14 @@ EOF
- - - - - + $user_unix_mapping_row + + $user_group_checkbox_rows +
- UNIX user: - - -
Groups belonged to: -$group_list
@@ -2685,6 +3141,7 @@ EOF } elsif( $c[1] eq 'groups' ) { + my $groups_service_guard_rows = mars_nwe_service_guard_rows( 'groups' ); $server = get_server(); if( $c[2] eq '' ) @@ -2722,12 +3179,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
Groups @@ -2830,12 +3288,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
@@ -2895,6 +3354,7 @@ EOF } elsif( $c[1] eq 'queues' ) { + my $queues_service_guard_rows = mars_nwe_service_guard_rows( 'print queues' ); $server = get_server(); if( $c[2] eq '' ) @@ -2932,12 +3392,13 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; } $settings_nav_bar - +@{[ settings_message_html() ]}
Print queues @@ -3028,6 +3489,7 @@ INPUT[type=SUBMIT], INPUT[type=RESET], INPUT[type=BUTTON] { } A { color:#9f2f26; } TT { color:#5b4b38; } +.smart-message { margin:0 0 12px 0; padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; font-weight:bold; }