File manager - Edit - /usr/local/cpanel/whostmgr/docroot/cgi/hostaccess.cgi
Back
#!/usr/local/cpanel/3rdparty/bin/perl # Copyright 2026 WebPros International, LLC # All rights reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cPanel license. Unauthorized copying is prohibited. use strict; use warnings; use Cpanel::Encoder::Tiny (); use Cpanel::Form (); use Cpanel::OS (); use Whostmgr::ACLS (); use Whostmgr::HTMLInterface (); # How many empty rows the rule form offers for new entries. The template # renders them after the existing rules and numbers them past the end of the # list, and savehostaccesslist() has to accept those numbers back, so both # sides read it from here rather than each hardcoding ten. use constant BLANK_RULE_ROWS => 10; # The highest action index the form can submit for a rule. Its JavaScript adds # one input at a time, numbered by how many actions are already filled in, and # a hosts.allow line carries a handful of directives -- this is far above any # real value while still keeping the array bounded. use constant MAX_ACTION_INDEX => 63; if ( !check_acls('all') ) { html_error( 403, "Forbidden" ); require Cpanel::Exit; Cpanel::Exit::exit_with_stdout_closed_first(); } my %FORM = Cpanel::Form::parseform(); if ( $FORM{'add_nftables_rule'} ) { eval { require Cpanel::XTables; my $obj = Cpanel::XTables->new( 'chain' => 'cPanel-HostAccessControl', ); if ( !$obj->table_exists( 'name' => 'filter', 'family' => 'inet' ) ) { my $create_line = [ [qw'add table inet filter'] ]; $obj->exec_checked_calls($create_line); } $obj->init_chain() if !$obj->chain_exists(); $obj->add_rule( 'port' => $FORM{'port'}, 'ip' => $FORM{'ip'}, 'protocol' => $FORM{'protocol'}, 'action' => $FORM{'action'}, ); }; if ($@) { html_error( 500, $@ ); require Cpanel::Exit; Cpanel::Exit::exit_with_stdout_closed_first(); } $FORM{'no_cache'} = 1; } elsif ( $FORM{'delete_nftables_rule'} ) { eval { require Cpanel::XTables; Cpanel::XTables->new( 'chain' => 'cPanel-HostAccessControl', )->delete_rule( 'handle' => $FORM{'delete_nftables_rule'}, ); }; # Without this the delete path dies at top level, before any header is # emitted, and the client gets a response with no status and no content # type at all. if ($@) { html_error( 500, $@ ); require Cpanel::Exit; Cpanel::Exit::exit_with_stdout_closed_first(); } $FORM{'no_cache'} = 1; } if ( $FORM{'fetch_nftables_rules'} ) { my $rules = []; eval { require Cpanel::JSON; require Cpanel::XTables; my $obj = Cpanel::XTables->new( 'chain' => 'cPanel-HostAccessControl' ); $obj->clear_ruleset_cache(); $rules = $obj->get_rules; }; if ($@) { push @$rules, { 'error' => $@ }; print "Status: 500\r\nContent-type: application/json\r\n\r\n"; } else { print "Status: 200\r\nContent-type: application/json\r\n\r\n"; } print Cpanel::JSON::pretty_dump($rules); require Cpanel::Exit; Cpanel::Exit::exit_with_stdout_closed_first(); } print "Content-type: text/html\r\n\r\n"; do_main(); sub do_main { require Cpanel::Template; my $saved; if ( exists $FORM{'save_accesslist'} ) { $saved = savehostaccesslist(); } if ( Cpanel::OS::supports_hostaccess() ) { require Cpanel::HostAccessLib; my $hostaccesslib = Cpanel::HostAccessLib->new; Cpanel::Template::process_template( 'whostmgr', { 'template_file' => 'host_access/hostaccess.tmpl', 'data' => { 'action' => q{}, 'saved' => $saved, 'services' => $hostaccesslib->fetch_services(), 'actions' => $hostaccesslib->fetch_actions(), 'wildcards' => $hostaccesslib->fetch_wildcards(), 'rules' => $hostaccesslib->{'DB'}, 'blank_rows' => BLANK_RULE_ROWS, 'emptyrule' => { 'daemon_list' => [], 'client_list' => [], 'action_list' => [], 'comment' => '', 'type' => 'empty_action', }, }, }, ); } else { # Host Access Controls not supported on CentOS 8+, so we # have a different implementation based on nftables. Cpanel::Template::process_template( 'whostmgr', { 'template_file' => 'host_access/nftables_access.tmpl', 'data' => { # The template drops this into a JavaScript object literal, # and Template Toolkit does not escape by default, so a # request parameter passed through verbatim would execute. # It is only ever a flag, so reduce it to one. 'no_cache' => $FORM{'no_cache'} ? 1 : 0, }, }, ); } return; } sub savehostaccesslist { require Cpanel::HostAccessLib; my $hostaccesslib = Cpanel::HostAccessLib->new; for ( my $i = 0; $i <= $#{ $hostaccesslib->{'DB'} }; $i++ ) { ${ $hostaccesslib->{'DB'} }[$i]->{'linenum'} = ( $i + 1 ); foreach my $list ( 'daemon_list', 'action_list', 'client_list' ) { if ( defined ${ $hostaccesslib->{'DB'} }[$i]->{$list} ) { ${ $hostaccesslib->{'DB'} }[$i]->{$list} = []; } } } foreach my $key ( keys %FORM ) { my $evalue = $FORM{$key}; my ( $slinenum, $element ) = split( /-/, $key, 2 ); next if ( !defined $slinenum || $slinenum !~ /^\d+$/ || !defined $element ); # The form renders every existing rule and then BLANK_RULE_ROWS empty # ones, numbered past the end of the list. Writing into those slots is # how a rule gets added -- commit() skips any entry that still has an # empty daemon or client list -- so the bound has to leave room for # them. It exists at all because a number outside that window is not # something the form can produce: zero decrements to -1 and silently # rewrites the last entry, and a large one autovivifies every slot up # to it before the loops below serialize the lot back out. next if $slinenum < 1 || $slinenum > scalar( @{ $hostaccesslib->{'DB'} } ) + BLANK_RULE_ROWS; my $linenum = int $slinenum; $linenum--; my ( $keyname, $keynum ) = split( /_/, $element, 2 ); if ( $keyname eq 'daemon' ) { ${ $hostaccesslib->{'DB'} }[$linenum]->{'daemon_list'} = Cpanel::HostAccessLib::daemon_parse($evalue); } elsif ( $keyname eq 'client' ) { ${ $hostaccesslib->{'DB'} }[$linenum]->{'client_list'} = Cpanel::HostAccessLib::client_parse($evalue); } elsif ( $keyname eq 'action' ) { # Same hazard as the line number, and the same reasoning: the form # allocates these indices densely from zero, one input at a time, # so anything outside that range is not a value it can submit. next if !defined $keynum || $keynum =~ tr/0-9//c || $keynum > MAX_ACTION_INDEX; my $newval = Cpanel::HostAccessLib::ptrim($evalue); next if ( $newval eq '' ); ${ ${ $hostaccesslib->{'DB'} }[$linenum]->{'action_list'} }[$keynum] = $newval; } elsif ( $keyname eq 'comment' ) { ${ $hostaccesslib->{'DB'} }[$linenum]->{'comment'} = $evalue; } } foreach my $line ( split( /:/, $FORM{'eventlist'} ) ) { my ( $linenum, $offset ) = split( /,/, $line ); foreach ( my $i = 0; $i <= $#{ $hostaccesslib->{'DB'} }; $i++ ) { if ( ${ $hostaccesslib->{'DB'} }[$i]->{'linenum'} == $linenum ) { my $newpt = ( $i + $offset ); my @nl = splice( @{ $hostaccesslib->{'DB'} }, $i, 1 ); splice( @{ $hostaccesslib->{'DB'} }, $newpt, 0, @nl ); last; } } } for ( my $i = 0; $i <= $#{ $hostaccesslib->{'DB'} }; $i++ ) { foreach my $list ( 'daemon_list', 'action_list', 'client_list' ) { if ( !defined ${ $hostaccesslib->{'DB'} }[$i]->{$list} ) { ${ $hostaccesslib->{'DB'} }[$i]->{$list} = []; } } if ( !defined ${ $hostaccesslib->{'DB'} }[$i]->{'type'} ) { ${ $hostaccesslib->{'DB'} }[$i]->{'type'} = 'access_list'; } } $hostaccesslib->reserialize(); $hostaccesslib->commit(); return 1; } sub check_acls { my @acls = @_; Whostmgr::ACLS::init_acls(); return scalar( grep { Whostmgr::ACLS::checkacl($_) } @acls ) == scalar(@acls); } sub html_error { my ( $code, $msg ) = @_; # The message can carry request data verbatim: a failed 'nft' call reports # the whole command line, which includes the submitted port, ip, protocol # and action. It must be HTML-encoded, otherwise those parameters are a # reflected XSS vector. my $safe_msg = Cpanel::Encoder::Tiny::safe_html_encode_str( _error_text($msg) ); print "Status: $code\r\nContent-type: text/html\r\n\r\n"; Whostmgr::HTMLInterface::defheader(); print "<h1>$safe_msg</h1>"; Whostmgr::HTMLInterface::sendfooter(); return; } sub _error_text { my ($err) = @_; # Take the message, never the stringified object: Cpanel::Exception's # overload appends a stack trace listing the arguments each frame was called # with, so rendering "$err" would put the submitted values back on the page # even when the message itself is clean. The ID and class belong in the # logs, not in front of an administrator. return $err->to_string_no_id() if ref $err && eval { $err->can('to_string_no_id') }; # Drop the source location Perl appends to a die() that has no trailing # newline. A module path and line number tell an administrator nothing and # describe our internals to anyone else. my $text = defined $err ? "$err" : q{}; $text =~ s{\s*at \S+ line [0-9]+\.?\s*\z}{}; return $text; }
| ver. 1.4 |
Github
|
.
| PHP 8.1.34 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings