Files
mars-smart/apply.pl
2026-05-22 15:02:58 +02:00

1794 lines
49 KiB
Perl

#
# SMArT
#
# Apply changes
#
# 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
#
#
use Fcntl qw( O_WRONLY O_CREAT O_TRUNC );
sub read_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 write_smart_bindery_values( $$$ )
{
my( $bind_server, $bind_user, $bind_pass ) = @_;
$bind_server = '' unless defined( $bind_server );
$bind_user = '' unless defined( $bind_user );
$bind_pass = '' unless defined( $bind_pass );
open( my $fh, '>' . $smart_nwclient_path ) or die "Could not open $smart_nwclient_path: $!";
print( $fh $bind_server . '/' . $bind_user . ' ' . $bind_pass . "\n" );
close( $fh );
chown( scalar( getpwnam( $nonroot_user ) ), 0, $smart_nwclient_path );
chmod( 0600, $smart_nwclient_path );
}
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 <<EOF;
HTTP/1.0 409 Conflict
Content-Type: text/html
$server_id
<HTML>
<HEAD>
<TITLE>SMArT Service required</TITLE>
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
<style>
BODY { background:#f6f2ea; color:#2e261d; font-family: Arial, Helvetica, sans-serif; margin:0; padding:24px; }
.box { max-width:760px; margin:30px auto; background:#fbf7f1; border:1px solid #ddcfba; border-radius:18px; overflow:hidden; box-shadow:0 6px 18px rgba(80,55,30,0.08); }
.head { background:#d7c0a0; padding:14px 16px; font-weight:bold; font-size:22px; }
.body { padding:16px; line-height:1.5; }
.actions { padding:14px 16px; background:#d7c0a0; }
a.button { display:inline-block; margin-right:10px; padding:8px 14px; border-radius:10px; background:#a32020; color:#fff; text-decoration:none; font-weight:bold; }
a.back { background:#6d5d53; }
TT { color:#5b4b38; }
</style>
</HEAD>
<BODY>
<div class="box">
<div class="head">MARS_NWE service is not running</div>
<div class="body">
<p>Cannot manage <b>$what</b> while <tt>mars-nwe-serv.service</tt> is stopped.</p>
<p>Start the MARS_NWE service first, then retry the operation.</p>
</div>
<div class="actions">
<a class="button" href="/static/start.html" target="OPTS">Open service page</a>
<a class="button back" href="javascript:history.back()">Back</a>
</div>
</div>
</BODY>
</HTML>
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/&/&amp;/g;
$s =~ s/</&lt;/g;
$s =~ s/>/&gt;/g;
$s =~ s/"/&quot;/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| <div class="row"><div class="key">$key</div><div class="val">$val</div></div>\n|;
}
$title = apply_html_escape( $title );
$msg = apply_html_escape( $msg );
print <<EOF;
HTTP/1.0 400 Bad Request
Content-Type: text/html
$server_id
<HTML>
<HEAD>
<TITLE>SMArT validation error</TITLE>
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
<style>
BODY { background:#f6f2ea; color:#2e261d; font-family: Arial, Helvetica, sans-serif; margin:0; padding:24px; }
.box { max-width:820px; margin:30px auto; background:#fbf7f1; border:1px solid #ddcfba; border-radius:18px; overflow:hidden; box-shadow:0 6px 18px rgba(80,55,30,0.08); }
.head { background:#d7c0a0; padding:14px 16px; font-weight:bold; font-size:22px; }
.body { padding:16px; line-height:1.5; }
.err { padding:10px 12px; border:1px solid #e3b0a6; border-radius:12px; background:#fff0ec; color:#7d251e; margin-bottom:14px; }
.details { border:1px solid #eadfce; border-radius:12px; overflow:hidden; background:#fffdf9; }
.row { display:grid; grid-template-columns:190px 1fr; gap:10px; padding:8px 10px; border-top:1px solid #eadfce; }
.row:first-child { border-top:0; }
.key { font-weight:bold; }
.val { font-family: monospace; color:#5b4b38; word-break:break-word; }
.actions { padding:14px 16px; background:#d7c0a0; }
a.button { display:inline-block; margin-right:10px; padding:8px 14px; border-radius:10px; background:#6d5d53; color:#fff; text-decoration:none; font-weight:bold; }
</style>
</HEAD>
<BODY>
<div class="box">
<div class="head">$title</div>
<div class="body">
<div class="err">$msg</div>
<div class="details">
$details_html </div>
</div>
<div class="actions">
<a class="button" href="javascript:history.back()">Back</a>
</div>
</div>
</BODY>
</HTML>
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/<queue>.
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 = queue_test_html_escape( $output );
print <<EOF;
HTTP/1.0 500 Internal Server Error
Content-Type: text/html
$server_id
<HTML>
<HEAD>
<TITLE>SMArT bindery command failed</TITLE>
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
<style>
BODY { background:#f6f2ea; color:#2e261d; font-family: Arial, Helvetica, sans-serif; margin:0; padding:24px; }
.box { max-width:900px; margin:30px auto; background:#fbf7f1; border:1px solid #ddcfba; border-radius:18px; overflow:hidden; box-shadow:0 6px 18px rgba(80,55,30,0.08); }
.head { background:#d7c0a0; padding:14px 16px; font-weight:bold; font-size:22px; }
.body { padding:16px; line-height:1.5; }
.err { padding:10px 12px; border:1px solid #e3b0a6; border-radius:12px; background:#fff0ec; color:#7d251e; margin-bottom:14px; }
.details { border:1px solid #eadfce; border-radius:12px; overflow:hidden; background:#fffdf9; }
.row { display:grid; grid-template-columns:150px 1fr; gap:10px; padding:8px 10px; border-top:1px solid #eadfce; }
.row:first-child { border-top:0; }
.key { font-weight:bold; }
.val { font-family: monospace; color:#5b4b38; white-space:pre-wrap; word-break:break-word; }
.actions { padding:14px 16px; background:#d7c0a0; }
a.button { display:inline-block; margin-right:10px; padding:8px 14px; border-radius:10px; background:#6d5d53; color:#fff; text-decoration:none; font-weight:bold; }
a.red { background:#a32020; }
</style>
</HEAD>
<BODY>
<div class="box">
<div class="head">$title</div>
<div class="body">
<div class="err">A MARS_NWE bindery command returned an error. The command output was also written to the SMArT log.</div>
<div class="details">
<div class="row"><div class="key">Command</div><div class="val">$cmd</div></div>
<div class="row"><div class="key">Output</div><div class="val">$output</div></div>
</div>
</div>
<div class="actions">
<a class="button red" href="/static/start.html" target="OPTS">Open service page</a>
<a class="button" href="javascript:history.back()">Back</a>
</div>
</div>
</BODY>
</HTML>
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 = queue_test_html_escape( $message );
$back_url = apply_html_escape( $back_url );
print <<EOF;
HTTP/1.0 200 OK
Content-Type: text/html
$server_id
<HTML>
<HEAD>
<TITLE>SMArT bindery operation completed</TITLE>
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
<style>
BODY { background:#f6f2ea; color:#2e261d; font-family: Arial, Helvetica, sans-serif; margin:0; padding:24px; }
.box { max-width:820px; margin:30px auto; background:#fbf7f1; border:1px solid #ddcfba; border-radius:18px; overflow:hidden; box-shadow:0 6px 18px rgba(80,55,30,0.08); }
.head { background:#d7c0a0; padding:14px 16px; font-weight:bold; font-size:22px; }
.body { padding:16px; line-height:1.5; }
.ok { padding:10px 12px; border:1px solid #b8d9a8; border-radius:12px; background:#edf7e8; color:#2f5b24; margin-bottom:14px; font-weight:bold; }
.details { border:1px solid #eadfce; border-radius:12px; overflow:hidden; background:#fffdf9; }
.row { display:grid; grid-template-columns:190px 1fr; gap:10px; padding:8px 10px; border-top:1px solid #eadfce; }
.row:first-child { border-top:0; }
.key { font-weight:bold; }
.val { font-family: monospace; color:#5b4b38; word-break:break-word; }
.actions { padding:14px 16px; background:#d7c0a0; }
a.button { display:inline-block; margin-right:10px; padding:8px 14px; border-radius:10px; background:#6d5d53; color:#fff; text-decoration:none; font-weight:bold; }
a.red { background:#a32020; }
</style>
</HEAD>
<BODY>
<div class="box">
<div class="head">$title</div>
<div class="body">
<div class="ok">$message</div>
<div class="details">
<div class="row"><div class="key">Bindery commands</div><div class="val">$count completed successfully</div></div>
<div class="row"><div class="key">Log</div><div class="val">Details were written to the SMArT log.</div></div>
</div>
</div>
<div class="actions">
<a class="button red" href="$back_url">Back to list</a>
<a class="button" href="/static/start.html" target="OPTS">Open service page</a>
</div>
</div>
</BODY>
</HTML>
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;
# nwbprm is used as cleanup before setting a property again. If the
# property is already absent, nwbprm can return a failure with
# "Could not delete the property". That is harmless here and should not
# abort the whole save operation.
if( defined( $cmd[0] ) && $cmd[0] eq 'nwbprm' && $output =~ /Could not delete the property/i )
{
apply_log_line( 'INFO', 'bindery command skipped missing property cmd=' . $cmdline . ' output=' . $output );
return 1;
}
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_bindery_pipe( $$ )
{
my( $cmd, $payload ) = @_;
my $eval_error = '';
my $result = eval
{
$cmd = '' unless defined( $cmd );
$payload = '' unless defined( $payload );
my $log_payload = $payload;
$log_payload =~ s/\r/\\r/g;
$log_payload =~ s/\n/\\n/g;
$log_payload = substr( $log_payload, 0, 500 ) . '...' if length( $log_payload ) > 500;
apply_log_line( 'INFO', 'bindery pipe command: ' . $cmd . ' payload=' . $log_payload );
apply_log_line( 'INFO', 'bindery pipe trace: entered run_bindery_pipe' );
my $tmpdir = '/run/mars-nwe-webui';
$tmpdir = '/tmp' if ! -d $tmpdir || ! -w $tmpdir;
my $tmp = $tmpdir . '/smart-bindery-' . $$ . '-' . int( rand( 1000000 ) ) . '.in';
apply_log_line( 'INFO', 'bindery pipe trace: temp file ' . $tmp );
my $tfh;
if( ! sysopen( $tfh, $tmp, O_WRONLY | O_CREAT | O_TRUNC, 0600 ) )
{
my $output = 'could not create temporary input file ' . $tmp . ': ' . $!;
apply_log_line( 'ERROR', 'bindery pipe command failed cmd=' . $cmd . ' output=' . $output );
bindery_error_page( 'Bindery command failed', $cmd, $output );
return 0;
}
apply_log_line( 'INFO', 'bindery pipe trace: temp file opened' );
apply_log_line( 'INFO', 'bindery pipe trace: writing payload via syswrite length=' . length( $payload ) );
my $offset = 0;
my $len = length( $payload );
while( $offset < $len )
{
my $written = syswrite( $tfh, $payload, $len - $offset, $offset );
if( ! defined( $written ) )
{
my $output = 'could not write temporary input file ' . $tmp . ': ' . $!;
close( $tfh );
unlink( $tmp );
apply_log_line( 'ERROR', 'bindery pipe command failed cmd=' . $cmd . ' output=' . $output );
bindery_error_page( 'Bindery command failed', $cmd, $output );
return 0;
}
if( $written == 0 )
{
my $output = 'could not write temporary input file ' . $tmp . ': zero bytes written';
close( $tfh );
unlink( $tmp );
apply_log_line( 'ERROR', 'bindery pipe command failed cmd=' . $cmd . ' output=' . $output );
bindery_error_page( 'Bindery command failed', $cmd, $output );
return 0;
}
$offset += $written;
}
apply_log_line( 'INFO', 'bindery pipe trace: payload written bytes=' . $offset );
if( ! close( $tfh ) )
{
my $output = 'could not close temporary input file ' . $tmp . ': ' . $!;
unlink( $tmp );
apply_log_line( 'ERROR', 'bindery pipe command failed cmd=' . $cmd . ' output=' . $output );
bindery_error_page( 'Bindery command failed', $cmd, $output );
return 0;
}
apply_log_line( 'INFO', 'bindery pipe trace: temp file closed' );
my $shell_cmd = $cmd . ' < ' . apply_shell_quote( $tmp );
apply_log_line( 'INFO', 'bindery pipe shell command: ' . $shell_cmd );
my $output = `$shell_cmd 2>&1`;
my $rc = $?;
apply_log_line( 'INFO', 'bindery pipe trace: shell command returned rc=' . ( $rc >> 8 ) );
unlink( $tmp );
apply_log_line( 'INFO', 'bindery pipe trace: temp file removed' );
$output = '' unless defined( $output );
$output =~ s/\r//g;
if( $rc != 0 )
{
my $exit = $rc >> 8;
apply_log_line( 'ERROR', 'bindery pipe command failed rc=' . $exit . ' cmd=' . $cmd . ' output=' . $output );
bindery_error_page( 'Bindery command failed', $cmd, $output );
return 0;
}
$bindery_cmd_ok_count++;
apply_log_line( 'INFO', 'bindery pipe command ok cmd=' . $cmd . ( $output ne '' ? ' output=' . $output : '' ) );
return 1;
};
if( ! $result )
{
$eval_error = $@ if defined( $@ ) && $@ ne '';
if( $eval_error ne '' )
{
apply_log_line( 'ERROR', 'bindery pipe command died cmd=' . $cmd . ' error=' . $eval_error );
bindery_error_page( 'Bindery command failed', $cmd, 'internal error while running bindery pipe command: ' . $eval_error );
return 0;
}
return 0;
}
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' )
{
setc( 2, server_name );
setc( 3, internal_net );
if( defined( $p{sync_smart_bind_server} ) && $p{sync_smart_bind_server} eq 'on' && defined( $p{server_name} ) && $p{server_name} ne '' )
{
my( $old_bind_server, $old_bind_user, $old_bind_pass ) = read_smart_bindery_values();
write_smart_bindery_values( $p{server_name}, $old_bind_user, $old_bind_pass );
}
setc( 16, test );
setc( 210, timing_down );
setc( 211, timing_warn );
delconfigline( 6 );
if( $p{version} ne '' )
{
if( $p{burst_enabled} eq 'on' )
{ addconfigline( '6 ' . $p{version} . ' 1' ); }
else
{ addconfigline( '6 ' . $p{version} . ' 0' ); }
}
delconfigline( 30 );
if( $p{burst_read} ne '' )
{ addconfigline( '30 ' . $p{burst_read} . ' ' . $p{burst_write} ); }
}
elsif( $c[1] eq 'dirs' )
{
setc( 40, path_cache );
setc( 41, share_lock );
setc( 42, spool );
setc( 45, bindery );
setc( 46, attribute );
setc( 47, trustee );
}
elsif( $c[1] eq 'configh' )
{
setc( 60, max_conn );
setc( 61, max_vol );
setc( 63, max_dirbase );
if( $p{mmap} eq 'on' )
{ $p{mmap} = 1; }
else
{ $p{mmap} = 0; }
setc( 68, mmap );
setc( 69, sap );
setc( 70, net_serial );
setc( 71, net_app );
}
elsif( $c[1] eq 'security' )
{
setc( 7, encrypt );
delconfigline( 9 );
if( $p{creat_dir} ne '' )
{ addconfigline( '9 ' . $p{creat_dir} . ' ' . $p{creat_file} ); }
}
elsif( $c[1] eq 'susers' )
{
setc( 10, guest_group );
setc( 11, guest_user );
delconfigline( 12 );
if( ( $p{root_user} ne '' ) and ( $p{root_name} ne '' ) )
{ addconfigline( '12 ' . $p{root_name} . ' ' . $p{root_user} . ' ' . $p{root_password} ); }
delconfigline( 15 );
if( $p{map} ne '' )
{ addconfigline( '15 ' . $p{map} . ' ' . $p{map_password} ); }
}
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] );
}
if( $p{name} ne '' )
{
cbc( 'm' );
cbc( 'n' );
cbc( 'o' );
cbc( 'p' );
cbc( 'r' );
cbc( 'O' );
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_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] );
}
$p{interface} =~ s/[^-_\.A-Za-z0-9:\*]//g;
if( $p{number} ne '' )
{
addconfigline( '4 ' . $p{number} . ' ' . $p{interface} . ' ' . $p{frametype} . ' ' . $p{delay} );
}
redirect_msg( '/settings/devices', 'Device settings saved.' );
}
elsif( $c[1] eq 'logging' )
{
setc( 100, ipx );
setc( 101, nwserv );
setc( 102, ncpserv );
setc( 103, nwconn );
setc( 104, nwclient );
setc( 105, nwbind );
setc( 106, nwrouted );
setc( 200, startup );
setc( 201, general_log );
delconfigline( 202 );
addconfigline( 202 . ' ' . ( $p{g_creat} + 2 * $p{error} ) );
setc( 300, routing_interval );
setc( 301, routing_log );
delconfigline( 302 );
addconfigline( 302 . ' ' . ( $p{r_creat} + 2 * $p{r_file} ) );
}
elsif( $c[1] eq 'smart' )
{
if( $p{mars_config} ne $mars_config )
{
# Just append the line. Messy but easy. ;)
open( FILE, '>>' . $smart_conf_path ) or die "Could not open $smart_conf_path: $!";
print( FILE "\n" . '$mars_config = \'' . $p{mars_config} . '\';' . "\n" );
close( FILE );
}
write_smart_bindery_values( $p{bind_server}, $p{bind_user}, $p{bind_pass} );
if( defined( $p{sync_general_server_name} ) && $p{sync_general_server_name} eq 'on' && defined( $p{bind_server} ) && $p{bind_server} ne '' )
{
$p{server_name} = $p{bind_server};
setc( 2, server_name );
}
}
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' )
{
return( 1 ) if ! run_bindery_cmd( 'nwbocreate', '-S', $server, '-t', '1', '-o', $p{name} );
$c[2] = $p{name};
}
else
{
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... ;)
$id =~ s/[^ ][^ ]/$& /g;
$id = join( '', reverse( split( ' ', $id ) ) );
# $id =~ s/ //g;
if( scalar( keys( %p ) ) == 0 )
{
return( 1 ) if ! run_bindery_cmd( 'nwborm', '-S', $server, '-t', '1', '-o', $c[2] );
}
else
{
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} );
my $bindery_pipe_payload = <<EOF;
0001
$c[2]
GROUPS_I'M_IN
2
31
EOF
@g = grep( /^group_/, keys( %p ) );
foreach $g ( @g )
{
$g =~ s/^group_//;
$bindery_pipe_payload .= '0002' . "\n" . $g . "\n";
system( 'nwbpadd -S ' . $server . ' -o ' . $g . ' -t 2 -p GROUP_MEMBERS ' . $id . ' > /dev/null' );
}
return( 1 ) if ! run_bindery_pipe( 'nwbpset -S ' . $server, $bindery_pipe_payload );
my $bindery_pipe_payload = <<EOF;
0001
$c[2]
SECURITY_EQUALS
2
31
EOF
@g = grep( /^group_/, keys( %p ) );
foreach $g ( @g )
{
$g =~ s/^group_//;
$bindery_pipe_payload .= '0002' . "\n" . $g . "\n";
}
return( 1 ) if ! run_bindery_pipe( 'nwbpset -S ' . $server, $bindery_pipe_payload );
if( $p{password} ne '' )
{
$p = get_bindery_password();
open( FILE, '|' . 'nwpasswd -O ' . $c[2] . ' > /dev/null' );
print( STDERR 'nwpasswd -O ' . $c[2] . ' > /dev/null' . "\n" );
print( FILE $p . "\n" . $p{password} . "\n" . $p{password} . "\n" );
close( FILE );
}
}
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' )
{
return( 1 ) if ! run_bindery_cmd( 'nwbocreate', '-S', $server, '-t', '2', '-o', $p{name} );
my $bindery_pipe_payload = <<EOF;
0002
$p{name}
GROUP_MEMBERS
2
31
EOF
return( 1 ) if ! run_bindery_pipe( 'nwbpset -S ' . $server, $bindery_pipe_payload );
$c[2] = $p{name};
}
else
{
return( 1 ) if ! run_bindery_cmd( 'nwbprm', '-S', $server, '-t', '2', '-o', $c[2], '-p', 'IDENTIFICATION' );
return( 1 ) if ! run_bindery_cmd( 'nwbprm', '-S', $server, '-t', '2', '-o', $c[2], '-p', 'OBJ_SUPERVISORS' );
}
if( scalar( keys( %p ) ) == 0 )
{
return( 1 ) if ! run_bindery_cmd( 'nwborm', '-S', $server, '-t', '2', '-o', $c[2] );
}
else
{
write_property_string( $c[2], 2, 'IDENTIFICATION', 0, 30, $p{fullname} );
my $bindery_pipe_payload = <<EOF;
0002
$c[2]
OBJ_SUPERVISORS
2
31
EOF
@g = grep( /^man_/, keys( %p ) );
foreach $g ( @g )
{
my @g = split( '_', $g );
$bindery_pipe_payload .= $g[1] . "\n" . $g[2] . "\n";
}
return( 1 ) if ! run_bindery_pipe( 'nwbpset -S ' . $server, $bindery_pipe_payload );
}
bindery_success_page( 'Group saved', 'Group bindery settings were saved successfully.', '/settings/groups' );
return( 1 );
}
elsif( $c[1] eq 'queues' )
{
my $is_delete = scalar( keys( %p ) ) == 0;
if( defined( $p{name} ) || defined( $p{command} ) || defined( $p{spool} ) || defined( $p{unix_print} ) || defined( $p{spool_dir} ) )
{
return( 1 ) if ! validate_queue_params();
}
return( 1 ) if ! require_mars_nwe_service( 'print queues' );
$server = get_server();
if( defined( $p{test_print} ) )
{
open_bindery_command_log();
drop_root();
test_print_queue( $server, $c[2] );
return( 1 );
}
if( $is_delete )
{
# Section 21 is the nwserv.conf print queue definition:
# 21 QUEUE_NAME SPOOL_DIR UNIX_PRINT_COMMAND
# Delete it before drop_root(), while apply.pl can still write nwserv.conf.
apply_log_line( 'INFO', 'print queue config delete requested queue=' . $c[2] );
my( $conf_removed, $raw_removed ) = delconfigqueue( $c[2] );
apply_log_line( 'INFO', 'print queue config delete conf_removed=' . $conf_removed . ' raw_removed=' . $raw_removed . ' queue=' . $c[2] );
writeconfig();
}
else
{
if( $c[2] ne 'add_new' )
{
$p{name} = $c[2] if ! defined( $p{name} ) || $p{name} eq '';
}
$p{name} = normalize_queue_name( $p{name} );
if( $p{spool_dir} eq '' )
{
$p{spool_dir} = 'SYS:SYSTEM/' . $p{name} . '.QDR';
}
my( $conf_removed, $raw_removed ) = delconfigqueue( $p{name} );
my $queue_line = queue_config_line( $p{name}, $p{spool_dir}, $p{unix_print} );
apply_log_line( 'INFO', 'print queue config save queue=' . $p{name} . ' conf_replaced=' . $conf_removed . ' raw_replaced=' . $raw_removed . ' line=' . $queue_line );
addconfigline( $queue_line );
writeconfig();
}
open_bindery_command_log();
drop_root();
$server = get_server();
if( $is_delete )
{
if( queue_exists_in_bindery( $server, $c[2] ) )
{
return( 1 ) if ! run_bindery_cmd( 'nwborm', '-S', $server, '-t', '3', '-o', $c[2] );
}
else
{
apply_log_line( 'INFO', 'bindery queue removal skipped, queue not present in bindery: ' . $c[2] );
}
bindery_success_page( 'Print queue deleted', 'Print queue configuration and bindery object were removed if present.', '/settings/queues' );
return( 1 );
}
if( $c[2] eq 'add_new' )
{
return( 1 ) if ! run_bindery_cmd( 'nwbocreate', '-S', $server, '-t', '3', '-o', $p{name} );
$c[2] = $p{name};
my $bindery_pipe_payload = <<EOF;
0003
$c[2]
Q_SERVERS
2
31
EOF
return( 1 ) if ! run_bindery_pipe( 'nwbpset -S ' . $server, $bindery_pipe_payload );
my $bindery_pipe_payload = <<EOF;
0003
$c[2]
Q_USERS
2
31
0002
EVERYONE
EOF
return( 1 ) if ! run_bindery_pipe( 'nwbpset -S ' . $server, $bindery_pipe_payload );
my $bindery_pipe_payload = <<EOF;
0003
$c[2]
Q_OPERATORS
2
31
0001
SUPERVISOR
EOF
return( 1 ) if ! run_bindery_pipe( 'nwbpset -S ' . $server, $bindery_pipe_payload );
}
else
{
return( 1 ) if ! run_bindery_cmd( 'nwbprm', '-S', $server, '-t', '3', '-o', $c[2], '-p', 'Q_UNIX_PRINT' );
return( 1 ) if ! run_bindery_cmd( 'nwbprm', '-S', $server, '-t', '3', '-o', $c[2], '-p', 'Q_DIRECTORY' );
}
write_property_string( $c[2], 3, 'Q_UNIX_PRINT', 0, 31, $p{unix_print} );
write_property_string( $c[2], 3, 'Q_DIRECTORY', 0, 31, $p{spool_dir} );
bindery_success_page( 'Print queue saved', 'Print queue configuration and bindery settings were saved successfully.', '/settings/queues' );
return( 1 );
}
elsif( $c[1] eq 'advanced' )
{
my $advanced_category = $p{advanced_category};
$advanced_category = 'all' if ! defined( $advanced_category ) || $advanced_category eq '';
my $save_devices = ( $advanced_category eq 'all' || $advanced_category eq 'devices' );
my $save_security = ( $advanced_category eq 'all' || $advanced_category eq 'security' );
my $save_users = ( $advanced_category eq 'all' || $advanced_category eq 'users' );
my $save_queues = ( $advanced_category eq 'all' || $advanced_category eq 'queues' );
my $save_configh = ( $advanced_category eq 'all' || $advanced_category eq 'configh' );
my $save_stations = ( $advanced_category eq 'all' || $advanced_category eq 'stations' );
my $save_network = ( $advanced_category eq 'all' || $advanced_category eq 'network' );
if( $save_devices )
{
my $device_flags = 0;
$device_flags += 1 if $p{dev_keep} eq 'on';
$device_flags += 2 if $p{dev_auto} eq 'on';
$device_flags += 4 if $p{dev_remove_all} eq 'on';
delconfigline( 5 );
addconfigline( '5 ' . $device_flags ) if $device_flags != 0;
}
if( $save_security )
{
my $security_flags = 0;
$security_flags += 0x8 if $p{sec_supervisor_ignore} eq 'on';
$security_flags += 0x40 if $p{sec_2gb_free} eq 'on';
$security_flags += 0x200 if $p{sec_int17} eq 'on';
$security_flags += 0x2 if $p{sec_delete_open} eq 'on';
$security_flags += 0x4 if $p{sec_rename_open} eq 'on';
delconfigline( 8 );
addconfigline( '8 ' . $security_flags ) if $security_flags != 0;
}
if( $save_users )
{
my $bindery_flags = 0;
$bindery_flags += 1 if $p{bindery_empty_scripts} eq 'on';
delconfigline( 17 );
addconfigline( '17 ' . $bindery_flags ) if $bindery_flags != 0;
}
if( $save_queues )
{
my $queue_flags = 0;
$queue_flags += 1 if $p{queue_no_banner} eq 'on';
delconfigline( 18 );
addconfigline( '18 ' . $queue_flags ) if $queue_flags != 0;
delconfigline( 22 );
foreach my $line ( split( /\r?\n/, $p{print_servers} ) )
{
$line =~ s/^\s+//;
$line =~ s/\s+$//;
next if $line eq '';
$line =~ s/#.*//;
next if $line eq '';
addconfigline( '22 ' . $line );
}
}
if( $save_configh )
{
setc( 50, conversion_tables );
setc( 80, max_dir_search_handles );
}
if( $save_network )
{
setc( 310, watchdogs );
}
if( $save_stations )
{
setc( 400, station_file );
setc( 401, nearest_replies );
setc( 402, connect_replies );
}
}
else
{
putp();
return( 0 );
}
redirect( '/static/start.html' );
writeconfig();
}
sub add_mask_flag( $$$ )
{
my( $value, $param, $mask ) = @_;
if( defined( $p{$param} ) && $p{$param} ne '' )
{
$value |= $mask;
}
return $value;
}
sub addconfig_hex( $$ )
{
delconfigline( $_[0] );
addconfigline( $_[0] . ' 0x' . sprintf( '%x', $_[1] ) );
}
sub sanitize_simple_value( $ )
{
my $v = $_[0];
$v = '' unless defined $v;
$v =~ s/[\r\n]//g;
$v =~ s/^\s+//;
$v =~ s/\s+$//;
$v =~ s/[^-_\.\/\:\~A-Za-z0-9]//g;
return $v;
}
sub sanitize_number_value( $ )
{
my $v = $_[0];
$v = '' unless defined $v;
$v =~ s/[^0-9xXa-fA-F\-]//g;
return $v;
}
sub queue_config_name( $ )
{
my $line = $_[0];
$line = '' unless defined( $line );
$line =~ s/^\s*21\s+//i;
$line =~ s/^\s+//;
$line =~ s/\s+$//;
return ( split( /\s+/, $line, 2 ) )[0];
}
sub queue_test_html_escape( $ )
{
my $s = $_[0];
$s = '' unless defined( $s );
$s =~ s/&/&amp;/g;
$s =~ s/</&lt;/g;
$s =~ s/>/&gt;/g;
$s =~ s/"/&quot;/g;
return $s;
}
sub queue_test_result_page( $$$$ )
{
my( $queue, $ok, $message, $output ) = @_;
$queue = queue_test_html_escape( $queue );
$message = queue_test_html_escape( $message );
$output = queue_test_html_escape( $output );
my $title = $ok ? 'Print queue test sent' : 'Print queue test failed';
my $color = $ok ? '#2f5b24' : '#8a1f16';
my $bg = $ok ? '#edf7e8' : '#fff0ed';
print <<EOF;
HTTP/1.0 200 OK
Content-Type: text/html
$server_id
<HTML>
<HEAD>
<TITLE>SMArT Print queue test</TITLE>
<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
<style>
BODY { background:#f6f2ea; color:#2e261d; font-family: Arial, Helvetica, sans-serif; margin:0; padding:24px; }
.box { max-width:850px; margin:30px auto; background:#fbf7f1; border:1px solid #ddcfba; border-radius:18px; overflow:hidden; box-shadow:0 6px 18px rgba(80,55,30,0.08); }
.head { background:#d7c0a0; padding:14px 16px; font-weight:bold; font-size:22px; }
.body { padding:16px; }
.status { margin:0 0 14px 0; padding:12px 14px; border-radius:12px; background:$bg; color:$color; font-weight:bold; }
PRE { white-space:pre-wrap; background:#fffdf9; border:1px solid #ddcfba; border-radius:12px; padding:12px; overflow:auto; }
A.button { display:inline-block; margin-top:14px; padding:9px 14px; border-radius:10px; background:#b84434; color:white; text-decoration:none; font-weight:bold; }
</style>
</HEAD>
<BODY>
<div class="box">
<div class="head">$title</div>
<div class="body">
<div class="status">$message</div>
<p><b>Queue:</b> <tt>$queue</tt></p>
<p><b>Command output:</b></p>
<PRE>$output</PRE>
<a class="button" href="/settings/queues" target="OPTS">Back to print queues</a>
</div>
</div>
</BODY>
</HTML>
EOF
}
our $queue_test_command_rc = 0;
sub queue_test_shell_log_quote( $ )
{
my $s = $_[0];
$s = '' unless defined( $s );
$s =~ s/'/'"'"'/g;
return "'" . $s . "'";
}
sub run_queue_test_command( @ )
{
my @cmd = @_;
my $output = '';
my $tmpdir = '/run/mars-nwe-webui';
my $out;
my $shell;
my $rc;
$queue_test_command_rc = 0;
$tmpdir = '/tmp' if ! -d $tmpdir || ! -w $tmpdir;
$out = $tmpdir . '/smart-test-print-output-' . $$ . '-' . int( rand( 1000000 ) ) . '.txt';
$shell = join( ' ', map { queue_test_shell_log_quote( $_ ) } @cmd ) .
' > ' . queue_test_shell_log_quote( $out ) . ' 2>&1';
$rc = system( $shell );
$queue_test_command_rc = $rc;
if( open( my $ofh, '<', $out ) )
{
local $/ = undef;
$output = <$ofh>;
close( $ofh );
unlink( $out );
}
else
{
$output = 'could not read nprint output file ' . $out . ': ' . $!;
}
$output = '' unless defined( $output );
$output =~ s/\r//g;
return $output;
}
sub test_print_queue( $$ )
{
my( $server, $queue ) = @_;
my $eval_error = '';
my $ok = eval
{
$server = '' unless defined( $server );
$queue = normalize_queue_name( $queue );
if( $queue eq '' )
{
queue_test_result_page( $queue, 0, 'Queue name is empty.', '' );
return 1;
}
my $nprint = defined( $smart_nprint_path ) && $smart_nprint_path ne '' ? $smart_nprint_path : '/usr/bin/nprint';
if( ! -x $nprint )
{
my $msg = 'nprint helper not found or not executable: ' . $nprint;
apply_log_line( 'ERROR', 'print queue test failed queue=' . $queue . ' error=' . $msg );
queue_test_result_page( $queue, 0, $msg, '' );
return 1;
}
my $tmpdir = '/run/mars-nwe-webui';
$tmpdir = '/tmp' if ! -d $tmpdir || ! -w $tmpdir;
my $tmp = $tmpdir . '/smart-test-print-' . $$ . '-' . int( rand( 1000000 ) ) . '.txt';
if( ! open( my $fh, '>', $tmp ) )
{
my $msg = 'Could not create test print file ' . $tmp . ': ' . $!;
apply_log_line( 'ERROR', 'print queue test failed queue=' . $queue . ' error=' . $msg );
queue_test_result_page( $queue, 0, $msg, '' );
return 1;
}
my $ts = scalar( localtime( time() ) );
print( $fh "SMArT / MARS_NWE print queue test\n" );
print( $fh "Server: $server\n" );
print( $fh "Queue: $queue\n" );
print( $fh "Time: $ts\n" );
print( $fh "\nIf you can read this, the NetWare print queue accepted a test job.\n" );
if( ! close( $fh ) )
{
my $msg = 'Could not close test print file ' . $tmp . ': ' . $!;
unlink( $tmp );
apply_log_line( 'ERROR', 'print queue test failed queue=' . $queue . ' error=' . $msg );
queue_test_result_page( $queue, 0, $msg, '' );
return 1;
}
my @cmd = ( $nprint, '-S', $server, '-q', $queue, '-d', 'SMArT test print', $tmp );
apply_log_line( 'INFO', 'print queue test command: ' . join( ' ', map { queue_test_shell_log_quote( $_ ) } @cmd ) );
my $output = run_queue_test_command( @cmd );
unlink( $tmp );
if( $queue_test_command_rc != 0 )
{
my $exit = $queue_test_command_rc >> 8;
apply_log_line( 'ERROR', 'print queue test failed rc=' . $exit . ' queue=' . $queue . ' output=' . $output );
queue_test_result_page( $queue, 0, 'nprint returned an error.', $output );
return 1;
}
apply_log_line( 'INFO', 'print queue test ok queue=' . $queue . ( $output ne '' ? ' output=' . $output : '' ) );
queue_test_result_page( $queue, 1, 'Test job was submitted with nprint.', $output );
return 1;
};
if( ! $ok )
{
$eval_error = $@ if defined( $@ ) && $@ ne '';
$eval_error = 'unknown internal error' if $eval_error eq '';
apply_log_line( 'ERROR', 'print queue test died queue=' . ( defined( $queue ) ? $queue : '' ) . ' error=' . $eval_error );
queue_test_result_page( defined( $queue ) ? $queue : '', 0, 'Internal error while running queue test.', $eval_error );
return 0;
}
return 1;
}
sub queue_config_line( $$$ )
{
my( $name, $spool, $command ) = @_;
$name = normalize_queue_name( $name );
$spool = '' unless defined( $spool );
$command = '' unless defined( $command );
$spool =~ s/^\s+//;
$spool =~ s/\s+$//;
$command =~ s/^\s+//;
$command =~ s/\s+$//;
return '21 ' . $name . ' ' . $spool . ' ' . $command;
}
sub queue_exists_in_bindery( $$ )
{
my( $server, $name ) = @_;
$name = normalize_queue_name( $name );
return 0 if $name eq '';
my @queues = split( "\n", `nwbols -t 3 -S $server 2>/dev/null` );
foreach my $q ( @queues )
{
my @c = split( ' ', $q );
return 1 if defined( $c[0] ) && uc( $c[0] ) eq uc( $name );
}
return 0;
}
sub putp()
{
$cc =~ s/.*\?//;
$cc =~ s/&/\n/g;
print <<EOF;
HTTP/1.0 200 OK
Content-Type: text/plain
$cc
EOF
}
sub setc( $$ )
{
delconfigline( $_[0] );
if( $p{$_[1]} ne '' )
{ addconfigline( $_[0] . ' ' . $p{$_[1]} ); }
}
sub cbc( $ )
{
if( $p{$_[0]} ne '' )
{ $p{$_[0]} = $_[0]; }
}