Mini Shell
Direktori : /opt/sharedrads/ |
|
Current File : //opt/sharedrads/modify-account |
#!/usr/local/cpanel/3rdparty/bin/perl
# encoding: utf-8
#
# author: Kyle Yetter
#
# Copyright 2011 Kyle Yetter - InMotion Hosting, Inc. - kyley@inmotionhosting.com
#
=pod
=head1 modify-account
modify-account - Change the basic configuration of a cPanel account.
=head1 SYNOPSIS
modify-account [options] userna5 --property=NEW_VALUE ...
=head1 DESCRIPTION
Change the basic parameters of a cPanel account on this system.
=head1 OPTIONS
=head2 BASIC MODIFICATIONS
=over 8
=item B<-u>, B<--new-user>, B<--user>=I<NEW_NAME>
Change the account's user name to I<NEW_NAME>
=item B<-o>, B<--owner>=I<RESELLER>
Change the owner of the account to I<RESELLER>
=item B<-w>, B<--password>=I<PASSWORD>, B<--pw>=I<PASSWORD>
Change the password of the account to I<RESELLER>
=item B<-d>, B<--domain>=I<DOMAIN>
Change the user's primary domain to I<DOMAIN>
=item B<-t>, B<--theme>=I<THEME>
Change the user's cPanel theme to I<THEME>
=item B<-c>, B<--cgi> / B<-C>, B<--no-cgi>
Disable or enable CGI access for the user
=item B<-s>, B<--shell> / B<-S>, B<--no-shell>
Disable or enable shell/SSH access for the user
=item B<-i>, B<--ip>=I<ADDR|KEYWORD>
Assign IP address I<ADDR> to all domains on the user's account. Valid keywords
include 'shared' or 'free'. Use --man for full details.
=item B<-p>, B<--package>=I<PKG>
Upgrade/downgrade the user's account to the package named I<PKG>
=item B<-b>, B<--bw>, B<--bandwidth>=I<LIMITq>
Apply a bandwidth limit to the given account. The limit value can
be specified with a unit (e.g. `10GB'). If no unit is given,
the value is assumed to be in MB.
=item B<-q>, B<--quota>=I<QUOTA>
Apply a disk space quota to the given account. The quota value can
be specified with a unit (e.g. `10GB'). If no unit is given,
the value is assumed to be in MB.
=back
=head2 RESELLER OPTIONS
=over 8
=item B<-r>, B<--reseller> / B<-R>, B<--no-reseller>
Disable or enable reseller privileges for the user
=item B<-a>, B<--acl>=I<NAME>
Apply the reseller access control list I<NAME> to the user
=item B<--self>, B<--no-self>
Specify whether or not to make the user its own owner when setting up reseller privileges
=item B<--ns>[=I<ns1.dom.com>,I<ns2.dom.com>,...]
Assign default nameserver addresses for account controlled by the user.
If no addresses are specified, use the server-wide default nameservers.
=item B<--custom-ns>=I<dom.com>
Assume a standard custom nameserver set up based off of I<dom.com>.
This sets the reseller nameservers to ns1.I<dom.com> and ns2.I<dom.com>.
=item B<--allow>=I<priv1>,I<priv2>,... / B<--deny>=I<priv1>,I<priv2>,...
Enable or disable the given reseller privileges. The privileges should be specified
in a comma separated list. For a list of valid privilege names, run
modify-account --list-privileges
=back
=head2 SYSTEM INFORMATION
=over 8
=item B<--list-users>
Print the user name of all cPanel accounts on the server
=item B<--list-themes>
Print the list of known cPanel themes
=item B<--list-ips>
List all IP addresses allocated to the server. The shared (main) IP is
displayed in cyan, free IPs are in green, and used IPs are in red
=item B<--list-packages>
Print the list of package names defined on the server
=item B<--list-resellers>
Print the list of users with reseller privileges on the server
=item B<--list-acls>
Print the list of reseller privilege ACL names defined on the server
=item B<--summary>, B<--info>
Print overall account settings for the given account
=back
=head2 GENERAL OPTIONS
=over 8
=item B<--force>
Bypass the validation checks that are performed before submitting the requested
changes to WHM.
=item B<-D>, B<--debug>
Print the WHM API URI sent to the server and dump the resulting data structure
=item B<-b>, B<--bleach>, B<--no-color>
Do not use color escapes in the output
=item B<--man>
Show full man page documentation for this program.
=item B<-h>, B<--help>
You know the drill
=item B<-v>, B<--version>
Ditto
=back
=head1 NOTES
=head2 IP Assignment
By specifying the B<-i> or B<--ip> option, you can change an account's IP address.
The IP address is assumed to be allocated and running on the server. If it isn't,
modify-account can automatically attempt to add the IP if you specify the IP with
a subnet mask. The two methods of specifying a subnet mask are illustrated below:
modify-account userna5 -i 1.2.3.4/24
modify-account userna5 -i 1.2.3.4:255.255.255.0
As a shortcut, you may also specify a keyword instead of an actual IP address.
=over 8
=item B<shared>, B<main>
The account will be assigned with the server's shared IP as found in I</var/cpanel/mainip>
=item B<free>
If any are available, the next unassigned IP address in /etc/ipaddrpool will be
assigned to the account. It's not a good idea to do this on a shared server.
=back
=head1 EXAMPLES
For the examples below, assume I<whoevs5> is a cPanel user
on the server with primary domain I<whoevs.com>
=head2 BASIC MODIFICATIONS
=head3 Changing User Name
modify-account whoevs5 --user=newuser6
modify-account whoevs5 -u newuser6
=head3 Changing Primary Domain
modify-account whoevs5 --domain=new-site.com
modify-account whoevs5 -d new-site.com
=head3 Upgrading/Downgrading an Account
modify-account whoevs5 --package=Pro
modify-account whoevs5 -p Pro
=head3 Fixing Account Ownership
modify-account whoevs5 --owner=inmotion
modify-account whoevs5 -o inmotion
=head2 UPGRADES AND DOWNGRADES
=head3 Shared to VPS or Dedicated
modify-account whoevs5 --reseller --self --cgi --shell -p vps -a vps
modify-account whoevs5 --reseller --self --cgi --shell -p dedicated -a dedicated
=head3 VPS or Dedicated to Shared
modify-account whoevs5 --no-reseller -o inmotion -p Power --cgi --no-shell
=head2 WEBSITE TRANSFERS AND ACCOUNT MOVES
=head3 Importing a cPanel Package From Another Host
Assume I<blueuser> is a cPanel package youve downloaded from BlueHost.
On the InMotion side, the user name is I<blueus5> and the
user is on a I<Power> plan.
/scripts/killacct blueus5 y
/script/restorepkg blueuser
modify-account blueuser -u blueus5 -p Power -t x3 --no-reseller --cgi --no-shell
This kills the existing empty I<blueus5> account
B<(MAKE SURE IT'S EMPTY)>, unpacks the I<blueuser>
package that's sitting in F</home>, and then changes
the I<blueuser> account so that it has the following
properties:
* the user name is blueus5
* the account is on the Power plan
* the account has the x3 cPanel theme
* the account doesn't have reseller privileges
* but it does have CGI execution privileges
* and it doesn't have shell access
=cut
BEGIN { unshift @INC, '/usr/local/cpanel'; }
# since the local SSL certificate's probably self-signed, allow the query
# to skip certificate verification
$ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0;
our $VERSION = 1.5;
use strict;
use LWP::UserAgent;
use URI;
use File::Basename;
use File::Slurp qw( slurp read_dir );
use Getopt::Long;
use JSON::Syck;
use Data::Dumper;
use Term::ANSIColor;
use Pod::Usage;
use Term::ReadLine;
use Cpanel::Config::LoadCpUserFile ();
use Net::IP qw( ip_bintoip ip_get_mask ip_iptobin );
our $console = undef;
sub tidy($) {
my ( $str ) = @_;
$str =~ s(\A\r?\n)();
$str =~ s(\r?\n[ \t]*\z)();
$str =~ s(^\s*\| ?)()mg;
return $str;
}
sub agree($) {
my ( $question ) = @_;
$question = "$question [yes/no] ";
$console ||= Term::ReadLine->new( basename( $0 ) );
$console->newTTY( \*STDIN, \*STDERR );
while ( defined( $_ = $console->readline( $question ) ) ) {
if ( /^y/i ) {
return 1;
} elsif ( /^n/i ) {
return 0;
} else {
print STDERR "Please enter `yes' or `no'\n";
}
}
return 0;
}
# this module's missing sometimes, so check if it's available.
# If not, the script will fail in a hard-to-figure-out way.
eval {
require LWP::Protocol::https;
} or do {
print STDERR tidy q(
| The Perl module LWP::Protocol::https is not installed on this system.
| It can be installed via:
| /scripts/perlinstaller LWP::Protocol::https\n
);
if ( agree( "Do you want to try and install it now?" ) ) {
my @command = qw( /scripts/perlinstaller LWP::Protocol::https );
my $status = system( @command );
die "perlinstaller failed with exit status `$status'" unless $status == 0;
eval { require LWP::Protocol::https; } or do {
die "the perlinstaller command didn't fail, but I still can't load the LWP::Protocol::https Perl module";
};
} else {
die "cannot run without the LWP::Protocol::https Perl module";
}
};
####################################################################################################
##################################### Global Options and Values ####################################
####################################################################################################
# display extra debugging info if true
our $debug = 0;
# if true, don't colorize output with ANSI escape sequences
our $bleach = 0;
# proceed even if there are validation errors
our $force = 0;
# set to 1 if this script had to create a cPanel access hash
our $created_access_hash = 0;
# stores all error messages accumulated during argument validation
our @errors = ();
# stores warning/info messages accumulated during argument validation
our @warnings = ();
# stores the arguments to the modifyacct API call
our %account_params = ();
# the cPanel user name for the account we're messing with
our $user = undef;
# the cPanel user data
our $cp_data = undef;
# the cPanel user's domains
our @user_domains;
# set to 0 when a WHM api call fails
our $api_success = 1;
# the WHM access key to authenticate the calls -- read when needed
our $access_key = undef;
# change the user's IP if set to a value
our $ip = undef;
# holds the subnet mask if an IP is going to be added to the server
our $subnet_mask = undef;
# if 1, try to add $ip to the server with $subnet_mask
our $add_ip = 0;
# change the user's package if set to a value
our $package = undef;
# change the user's password
our $password = undef;
# change the user's bandwidth limit if set to a value
our $bandwidth = undef;
# change the user's disk quota if set to a value
our $quota = undef;
# control adding/removing reseller privileges
our $reseller = undef;
# when adding reseller privileges, this specifies whether the user owns itself or not
our $reseller_owns_self = undef;
# apply this reseller privilege ACL to the user when set
our $reseller_acl = undef;
# used to add or remove specific reseller privileges
our %privilege_changes;
# stores default nameserver values
our @nameservers;
# set to true if a summary print out is requested
our $account_summary = undef;
# check whether this is an IMH/HUB shared server to impose extra precautions
our $on_shared = 0;
our $server_name = `hostname`;
chomp( $server_name );
$server_name =~ s(\.(inmotionhosting|webhostinghub)\.com$)()i;
if ( $server_name =~ /^((?:ec)?biz|edge|[ew]?hub|gs|ld|sb)\d+/i ) {
$on_shared = 1;
}
END {
#
# If we had to create a temporary access hash to make this work,
# make sure it gets trashed
#
if ( -f '/root/.accesshash' && $created_access_hash ) {
unlink( '/root/.accesshash' ) or die( "failed to remove /root/.accesshash" );
}
}
####################################################################################################
##################################### Global Utility Functions #####################################
####################################################################################################
sub c($$) {
my ( $str, $style ) = @_;
$str = colored( $str, $style ) unless $bleach;
return $str;
}
sub d {
if ( $debug ) {
my ( $fmt, @params ) = @_;
local $\ = "\n";
my $message = c( sprintf( $fmt, @params ), 'cyan' );
print STDERR "DEBUG: $message";
}
}
sub read_delimited($$) {
my $path = shift;
my $delim = shift;
my %data;
for ( slurp( $path ) ) {
chomp;
s(^\s+|\s+$)()g; # strip the line
next if /^\s*(?:#.*)?$/; # skip over blank and comment lines
my ( $key, $value ) = split( /$delim/, $_, 2 );
$data{$key} = $value;
}
return %data;
}
####################################################################################################
#################################### Validation Helper Variables ###################################
####################################################################################################
our $domain_rx = qr((?:(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?)\.)*(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?)(?:\.(?:m(?:[acdeghkmnpqrstvwxyz]|u(?:seum)?|o(?:bi)?|i?l)|a(?:[cdfgilmnoqtuwxz]|e(?:ro)?|r(?:pa)?|s(?:ia)?)|c(?:[cdfghiklmnruvxyz]|o(?:op|m)?|at?)|t(?:[cdfghjkmnoptvwz]|r(?:avel)?|e?l)|n(?:[cfgilopruz]|a(?:me)?|et?)|b(?:[abdefghjmnorstvwyz]|iz?)|g(?:[abdefghilmnpqrstuwy]|ov)|i(?:[delmoqrst]|n(?:fo|t)?)|p(?:[aefghklmnstwy]|ro?)|s[abcdeghijklmnortuvyz]|j(?:[emp]|o(?:bs)?)|e(?:[cegrst]|d?u)|k[eghimnprwyz]|l[abcikrstuvy]|v[aceginu]|d[ejkmoz]|f[ijkmor]|h[kmnrtu]|o(?:rg|m)|u[agksyz]|r[eosuw]|z[amw]|w[fs]|y[et]|qa))+)i;
our $ip_rx = qr(^(?:(?:2(?:[0-4]\d|5[0-5])|1\d{2}|\d{1,2})\.){3}(?:2(?:[0-4][0-9]|5[0-5])|1\d{2}|\d{1,2})$);
our $netmask_rx = qr(^1+0+$);
our %lazy_load;
our %themes;
sub load_themes {
unless ( $lazy_load{ themes } ) {
%themes = map { basename( $_ ) => 1 } glob( "/var/cpanel/locale/themes/*/" );
$lazy_load{ themes } = 1;
}
return %themes;
}
our %ip_addresses;
our @ip_pool;
our $shared_ip;
our %used_ips;
sub load_ips {
unless ( $lazy_load{ ips } ) {
if ( -f "/var/cpanel/mainip" ) {
$shared_ip = slurp( "/var/cpanel/mainip" );
chomp( $shared_ip );
$ip_addresses{ $shared_ip } = 1;
} else {
d "cannot obtain shared IP: /var/cpanel/mainip does not exist";
}
if ( -f "/etc/ips" ) {
for my $entry ( slurp( "/etc/ips" ) ) {
my ( $ip ) = split( ':', $entry );
$ip_addresses{ $ip } = 1;
}
}
if ( -f "/etc/domainips" ) {
for my $entry ( slurp( "/etc/domainips" ) ) {
chomp $entry;
my ( $ip, $domain ) = split( /\s*:\s*/, $entry, 2 );
$used_ips{ $ip } ||= $domain;
}
}
if ( -f "/etc/ipaddrpool" ) {
for my $ip ( slurp( "/etc/ipaddrpool" ) ) {
chomp $ip;
push( @ip_pool, $ip ) unless $used_ips{ $ip };
$ip_addresses{ $ip } = 1;
}
}
$lazy_load{ ips } = 1;
}
return %ip_addresses;
}
our %ssl_domains;
sub load_ssl {
unless ( $lazy_load{ ssl } ) {
if ( -f "/etc/ssldomains" ) {
%ssl_domains = read_delimited( "/etc/ssldomains", '\s*:\s*' );
} else {
d "cannot obtain SSL domains: /etc/ssldomains does not exist";
}
$lazy_load{ ssl } = 1;
}
return %ssl_domains;
}
our %packages;
sub load_packages {
unless ( $lazy_load{ packages } ) {
my $package_dir = "/var/cpanel/packages";
if ( -d $package_dir ) {
my @package_names = grep { -f "$package_dir/$_" } read_dir( $package_dir );
for my $name ( @package_names ) {
my $lower = lc( $name );
if ( $packages{ $lower } ) {
warn tidy qq(
| This server has packages with overlapping names:
| - $packages{$lower}
| - $name
| If you specify `$lower' as the package name, `$name' will be used
);
}
$packages{ $lower } = $name;
}
}
$lazy_load{ packages } = 1;
}
return %packages;
}
our %resellers;
our %reseller_acls;
# the set of all known cPanel reseller privilege names
our %PRIVILEGE_NAMES = map { $_ => 1 } qw(
add-pkg edit-dns rearrange-accts
add-pkg-ip edit-mx res-cart
add-pkg-shell edit-pkg resftp
all frontpage restart
allow-addoncreate kill-acct show-bandwidth
allow-parkedcreate kill-dns ssl
allow-unlimited-bw-pkgs limit-bandwidth ssl-buy
allow-unlimited-disk-pkgs list-accts ssl-gencrt
allow-unlimited-pkgs mailcheck stats
clustering mod-subdomains status
create-acct news suspend-accts
create-dns onlyselfandglobalpkgs thirdparty
demo-setup park-dns upgrade-account
disallow-shell passwd viewglobalpackages
edit-account quota
);
sub load_resellers {
delete $lazy_load{ resellers } if $_[ 0 ]; # force reload
unless ( $lazy_load{ resellers } ) {
%resellers = ();
%reseller_acls = ();
if ( -f "/var/cpanel/resellers" ) {
for ( slurp( "/var/cpanel/resellers" ) ) {
chomp;
my ( $reseller, $priv_list ) = split( /\s*:\s*/, $_, 2 );
my %privs;
$priv_list =~ s(^\s+|\s+$)()g;
$privs{ $_ } = 1 for split( /\s*,\s*/, $priv_list );
$resellers{ $reseller } = \%privs;
}
}
$resellers{ 'root' } = 1;
my $acl_dir = "/var/cpanel/acllists";
if ( -d $acl_dir ) {
my @acl_names = grep { -f "$acl_dir/$_" } read_dir( $acl_dir );
for my $name ( @acl_names ) {
my $lower = lc( $name );
if ( $reseller_acls{ $lower } ) {
warn tidy qq(
| This server has packages with overlapping names:
| - $reseller_acls{$lower}
| - $name
| If you specify `$lower' as the package name, `$name' will be used
);
}
$reseller_acls{ $lower } = $name;
}
}
$lazy_load{ resellers } = 1;
}
return %resellers;
}
our %users;
sub load_users {
unless ( $lazy_load{ users } ) {
my $user_dir = "/var/cpanel/users";
if ( -d $user_dir ) {
my @user_names = grep { -f "$user_dir/$_" } read_dir( $user_dir );
%users = map { $_ => 1 } @user_names;
}
$lazy_load{ users } = 1;
}
return %users
}
our %server_config;
our @default_nameservers;
sub load_server_config {
unless ( $lazy_load{ server_config } ) {
if ( -f "/etc/wwwacct.conf" ) {
%server_config = read_delimited( '/etc/wwwacct.conf', '\s+' );
my @ns_keys = qw( NS NS2 NS3 NS4 );
for ( @ns_keys ) {
my $ns_address = $server_config{ $_ };
$ns_address and push( @default_nameservers, $ns_address );
}
}
$lazy_load{ server_config } = 1;
}
return %server_config;
}
sub who_owns( $ ) {
my $domain = quotemeta( shift );
my $user = undef;
open( USERDOMAINS, "/etc/userdomains" ) or die "could not open /etc/userdomains: $!";
SEARCH:
while( <USERDOMAINS> ) {
if ( /^$domain: (\S+)/i ) {
$user = $1;
last SEARCH;
}
}
close( USERDOMAINS );
return $user;
}
####################################################################################################
##################################### Formatting/Output Helpers ####################################
####################################################################################################
our @BYTE_UNITS = qw( TB GB MB KB B );
our %BYTE_UNIT_ORDER = ();
for ( 0 .. $#BYTE_UNITS ) {
$BYTE_UNIT_ORDER{ $BYTE_UNITS[ $_ ] } = 2 ** ( 10 * ( $#BYTE_UNITS - $_ ) );
}
our %BYTE_UNIT_PRECISION = {
TB => 2, GB => 2, MB => 1,
KB => 0, B => 0
};
sub parse_bytes($;$$) {
my ( $byte_string, $target_unit, $default_unit ) = @_;
$byte_string = uc( $byte_string );
$target_unit ||= uc( $default_unit || 'B' );
$default_unit ||= uc( $default_unit || $target_unit );
unless ( exists $BYTE_UNIT_ORDER{ $default_unit } ) {
die "invalid unit name provided as the default unit: `$default_unit'";
}
my ( $value, $unit );
if ( $byte_string =~ /^\s*(\d*\.\d+|\d+)\s*([TKGM]?B)?/i ) {
( $value, $unit ) = ( $1, $2 );
$value += 0; # convert string to float
} elsif ( $byte_string =~ /(\d*\.\d+|\d+)/ ) {
$value = $1 + 0;
}
return convert_bytes( $value, $unit || $default_unit, $target_unit );
}
sub convert_bytes($$;$) {
my ( $value, $from_unit, $to_unit ) = @_;
$to_unit ||= 'B';
$from_unit = uc( $from_unit );
$to_unit = uc( $to_unit );
exists $BYTE_UNIT_ORDER{ $from_unit } or
die "invalid byte unit name provided: `$from_unit'";
exists $BYTE_UNIT_ORDER{ $to_unit } or
die "invalid byte unit name provided: `$to_unit'";
my $factor = $BYTE_UNIT_ORDER{ $from_unit } / $BYTE_UNIT_ORDER{ $to_unit };
return $value * $factor
}
sub byte_format($) {
my $bytes = shift;
my $size = $bytes / ( 1024.0 ** $#BYTE_UNITS );
for my $unit ( @BYTE_UNITS ) {
if ( $size >= 1 ) {
my $precision = $BYTE_UNIT_PRECISION{ $unit };
return sprintf( "%.${precision}f %2s", $size, $unit );
}
$size *= 1024;
}
return "$bytes B ";
}
sub center($$) {
my ( $string, $width ) = @_;
my $len = length( $string );
return $string if $width <= $len;
my $padding = $width - $len;
my $left = int( $padding / 2 );
my $right = $left + $padding % 2;
return( ( ' ' x $left ) . $string . ( ' ' x $right ) );
}
sub print_list_item($$) {
local $\ = "\n";
my $text = shift;
$text =~ s(\s+\z)();
my $color = shift;
my @lines = split( /\r?\n/, $text );
my $main = shift( @lines );
print c( "* $main", $color );
for ( @lines ) {
print " - $_";
}
}
sub print_table {
local $\ = "\n";
my @rows = @_;
my @arrays;
my @titles;
my $title = undef;
for my $item ( @rows ) {
if ( ref( $item ) eq 'ARRAY' ) {
push( @arrays, $item );
} elsif ( ref( \$item ) eq "SCALAR" ) {
push( @titles, $item );
}
}
unless ( @arrays ) {
$@ = "print_table: no rows provided";
return;
}
my $ncols = scalar( @{$arrays[ 0 ]} );
my @widths;
for my $c ( 0 .. ( $ncols - 1 ) ) {
my $w = 0;
for my $row ( @arrays ) {
my $col = $row->[$c];
my $col_width = length( $col );
if ( $w < $col_width ) { $w = $col_width; }
}
$widths[ $c ] = $w;
}
my $inner_width = ( $ncols - 1 ) * 3;
$inner_width += $_ for @widths;
if ( @titles ) {
my $title_width = 0;
for my $t ( @titles ) {
my $l = length( $t );
$title_width = $l if $l > $title_width;
}
if ( $title_width > $inner_width ) {
$widths[-1] += $title_width - $inner_width;
$inner_width = $title_width;
}
}
if ( ref( \$rows[0] ) eq "SCALAR" ) {
$title = shift( @rows );
print( '+-' . ( '-' x $inner_width ) . '-+' );
print( '| ' . center( $title, $inner_width ) . ' |' );
}
my $mask = '| ' . join( " | ", map { "%-${_}s" } @widths ) . " |";
my $border = '+-' . join( "-+-", map { '-' x $_ } @widths ) . "-+";
my $outline = '+-' . ( '-' x $inner_width ) . '-+';
my $after_title = 1;
for my $row ( @rows ) {
if ( ref( $row ) eq 'ARRAY' ) {
print( $border ) if $after_title;
print( sprintf( $mask, @{ $row } ) );
$after_title = 0;
} elsif ( ref( \$row ) eq 'SCALAR' ) {
print( $after_title ? $outline : $border );
print( '| ' . center( $row, $inner_width ) . ' |' );
$after_title = 1;
}
}
print( $after_title ? $outline : $border );
}
sub print_user_config( $ ) {
my $config = shift;
my $attrs = $config->{ cpuser };
my @table = ();
push( @table, [ "User", $config->{ user } ] );
push( @table, [ "Domain", $config->{ domain } ] );
push( @table, [ "Shell Access", $config->{ setshell } ] );
my @keys = sort keys( %$attrs );
for my $key ( @keys ) {
my $value = $attrs->{ $key };
unless ( $key eq "DOMAINS" or $key eq "DEADDOMAINS" ) {
push( @table, [ $key, "$value" ] );
}
}
print_table( "Updated Configuration", @table );
}
sub show_usage(;$) {
my $message = shift;
$message ? pod2usage( -msg => c( $message, 'red' ), -exitval => 1 ) : pod2usage( 0 );
}
sub compare_ips($$) {
my ( $a, $b ) = @_;
my @a_parts = split( /\./, $a, 4 );
my @b_parts = split( /\./, $b, 4 );
for my $i ( 0..3 ) {
my $ai = $a_parts[ $i ] + 0;
my $bi = $b_parts[ $i ] + 0;
my $c = $ai <=> $bi;
if ( $c ) { return $c; }
}
return 0;
}
####################################################################################################
######################################### WHM API Functions ########################################
####################################################################################################
sub access_key {
if ( $access_key ) { return $access_key; }
unless ( -f '/root/.accesshash' ) {
$ENV{ REMOTE_USER } = 'root';
`/usr/local/cpanel/bin/mkaccesshash`;
delete $ENV{ REMOTE_USER };
$created_access_hash = 1;
}
if ( -f '/root/.accesshash' ) {
$access_key = slurp( "/root/.accesshash" ) or die "could not read /root/.accesshash: $!";
$access_key =~ s/\n//g;
} else {
die "could not read /root/.accesshash: $!";
}
return $access_key;
}
sub load_json($) {
my ( $source ) = @_;
my $data;
eval { $data = JSON::Syck::Load( $source ); };
if ( $@ ) {
die( $@ . "\nJSON SOURCE:\n" . $source );
}
return $data;
}
sub api_report($$) {
my $function = shift;
my $result = shift;
my $success = $result->{ status } + 0; # 0 if failed, 1 if succeeded
my $message = $result->{ statusmsg };
my $warnings = $result->{ warnings };
my $messages = $result->{ messages } || $result->{ msgs };
my $status = $result->{ status } + 0; # 0 if failed, 1 if succeeded
my $user_config = $result->{ newcfg };
my $output = $result->{ rawout };
print c( $function, "cyan underline" ), "\n";
print c( $message, $success ? 'green' : 'red' ), "\n";
if ( $warnings ) { print_list_item( $_, "magenta" ) for @$warnings };
if ( $messages ) {
if ( ref( $messages ) eq 'ARRAY' ) {
print_list_item( $_, "yellow" ) for @$messages
} else {
print_list_item( $messages, "yellow" );
}
};
if ( $output ) { print $output, "\n"; }
if ( $user_config ) { print_user_config( $user_config ); }
}
sub api_call($%) {
my $function = shift;
my %params = @_;
my $direct = 0;
if ( exists $params{ -direct } ) {
$direct = $params{ -direct };
delete $params{ -direct };
}
my $uri = URI->new( "https://127.0.0.1:2087/json-api/$function" );
$uri->query_form( %params );
d "query URI: %s", $uri;
# read the WHM access key
my $auth = "WHM root:" . access_key;
my $ua = LWP::UserAgent->new;
my $request =
HTTP::Request->new( GET => "$uri" );
$ua->timeout(300);
$request->header( Authorization => $auth );
my $response = $ua->request( $request );
my $data = load_json( $response->content );
d "json data:\n%s", Dumper( $data ) if $debug; # keeps the Dumper call from executing unless totally necessary
return $data if $direct;
my $result = exists $data->{ result } ? $data->{ result } : $data;
ref( $result ) eq "ARRAY" and $result = $result->[ 0 ];
my $success = $result->{ status } + 0; # 0 if failed, 1 if succeeded
api_report( $function, $result );
$api_success &= $success;
return $result;
}
####################################################################################################
####################################### Command Line Parsing #######################################
####################################################################################################
# if no arguments whatsoever are provided, show the man page documentation
unless ( @ARGV ) {
pod2usage({
-exitval => 1,
-verbose => 2
});
}
my $option_parser = new Getopt::Long::Parser;
$option_parser->configure( 'bundling' );
$option_parser->getoptions(
'list-themes' => sub {
load_themes;
print "$_\n" for sort keys %themes;
exit( 0 );
},
'list-packages' => sub {
load_packages;
print "$_\n" for sort keys %packages;
exit( 0 );
},
'list-resellers' => sub {
load_resellers;
print "$_\n" for sort keys %resellers;
exit( 0 );
},
'list-acls' => sub {
load_resellers;
print "$_\n" for sort keys %reseller_acls;
exit( 0 );
},
'list-ips' => sub {
load_ips;
my @ips = sort { compare_ips( $a, $b ) } keys( %ip_addresses );
for my $ip ( @ips ) {
if ( $ip eq $shared_ip ) {
print c( "$ip\t(main)", 'cyan' ), "\n";
} elsif ( $used_ips{ $ip } ) {
print c( $ip, 'red' ), "\n";
} else {
print c( $ip, 'green' ), "\n";
}
}
exit( 0 );
},
'list-privileges' => sub {
print "$_\n" for sort keys %PRIVILEGE_NAMES;
exit( 0 );
},
'list-users' => sub {
load_users;
print "$_\n" for sort keys( %users );
exit( 0 );
},
'no-color|bleach|b' => \$bleach,
'debug|D' => \$debug,
'domain|d=s' => sub {
my ( $opt, $value ) = @_;
# TODO: add validation for the domain name argument
$account_params{ domain } = $value;
},
'new-user|user|u=s' => sub {
my ( $opt, $value ) = @_;
$account_params{ newuser } = $value;
},
'owner|o=s' => sub {
my ( $opt, $value ) = @_;
$account_params{ owner } = $value;
},
'theme|t=s' => sub {
my ( $opt, $value ) = @_;
$account_params{ CPTHEME } = $value;
},
'self!' => sub {
my ( $opt, $value ) = @_;
$reseller_owns_self = $value;
},
'max-ftp=i' => sub {
my ( $opt, $value ) = @_;
$account_params{ MAXFTP } = "$value";
},
'max-sql=i' => sub {
my ( $opt, $value ) = @_;
$account_params{ MAXSQL } = "$value";
},
'max-pop=i' => sub {
my ( $opt, $value ) = @_;
$account_params{ MAXPOP } = "$value";
},
'max-park=i' => sub {
my ( $opt, $value ) = @_;
$account_params{ MAXPARK } = "$value";
},
'max-sub=i' => sub {
my ( $opt, $value ) = @_;
$account_params{ MAXSUB } = "$value";
},
'max-addon=i' => sub {
my ( $opt, $value ) = @_;
$account_params{ MAXADDON } = "$value";
},
'c|cgi!' => sub {
my ( $opt, $value ) = @_;
$account_params{ HASCGI } = "$value";
},
'C' => sub {
my ( $opt, $value ) = @_;
$account_params{ HASCGI } = "0";
},
's|shell!' => sub {
my ( $opt, $value ) = @_;
$account_params{ shell } = "$value";
},
'S' => sub {
my ( $opt, $value ) = @_;
$account_params{ shell } = "0";
},
'r|reseller!' => sub {
my ( $opt, $value ) = @_;
$reseller = $value ? 'on' : 'off';
},
'R' => sub {
my ( $opt, $value ) = @_;
$reseller = 'off';
},
'acl|a=s' => \$reseller_acl,
'ns:s' => sub {
my ( $opt, $value ) = @_;
if ( $value ) {
push( @nameservers, $_ ) for split( /\s*,\s*|\s+/, $value );
} else {
load_server_config;
@nameservers = @default_nameservers;
}
},
'custom-ns=s' => sub {
my ( $opt, $value ) = @_;
@nameservers = ( "ns1.$value", "ns2.$value" );
},
'allow=s' => sub {
my ( $opt, $value ) = @_;
$value =~ s/^s+|\s+$//g;
$privilege_changes{ $_ } = 1 for split( /\s*,\s*|\s+/, $value );
},
'deny=s' => sub {
my ( $opt, $value ) = @_;
$value =~ s/^s+|\s+$//g;
$privilege_changes{ $_ } = 0 for split( /\s*,\s*|\s+/, $value );
},
'ip|i=s' => \$ip,
'package|p=s' => \$package,
'password|pw|w=s' => \$password,
'bandwidth|bw|b=s' => sub {
my ( $opt, $value ) = @_;
$bandwidth = int( parse_bytes( $value, 'MB' ) );
},
'quota|q=s' => sub {
my ( $opt, $value ) = @_;
$quota = int( parse_bytes( $value, 'MB' ) );
},
'force' => \$force,
'info|summary' => \$account_summary,
'help|h' => sub { show_usage(); },
'man' => sub {
pod2usage({
-exitval => 0,
-verbose => 2
})
},
'version|v' => sub {
print "$VERSION\n";
exit( 0 );
}
);
$user = shift( @ARGV ) or show_usage( "specify a cPanel user name" );
unless ( -f "/var/cpanel/users/$user" ) {
die "`$user' is not an existing cPanel user";
}
$cp_data = Cpanel::Config::LoadCpUserFile::loadcpuserfile( $user );
@user_domains = ( $cp_data->{ 'DOMAIN' }, @{ $cp_data->{ 'DOMAINS' } } );
####################################################################################################
######################################## Argument Validation #######################################
####################################################################################################
# New primary domain
if ( my $domain = $account_params{ domain } ) {
if ( $domain !~ /$domain_rx/i ) {
push( @errors, "`$domain' is not a valid domain name" );
} elsif ( my $owner = who_owns( $domain ) ) {
push( @errors, "`$domain' is already owned by user `$owner'" );
}
}
# New user name
if ( my $new_user = $account_params{ newuser } ) {
if ( $new_user !~ /^[a-z][a-z\d]*$/i ) {
push(
@errors,
"Bad user name `$new_user': must be entirely alphanumeric, starting with a letter"
);
} elsif ( length( $new_user ) > 8 ) {
push(
@errors,
"Bad user name `$new_user': must be 8 or less characters in length"
);
} elsif ( -f "/var/cpanel/users/$new_user" ) {
push( @errors, "user `$new_user' already exists on the system" );
}
}
# Changing reseller ownership
if ( my $owner = $account_params{ owner } ) {
load_resellers;
unless ( $owner eq 'root' or exists $resellers{ $owner } ) {
unless ( $owner eq $user and $reseller eq 'on' ) {
push( @errors, "`$owner' is not an existing reseller account" );
}
}
}
# Changing cPanel theme
if ( my $theme = $account_params{ CPTHEME } ) {
load_themes;
exists $themes{ $theme } or
push( @errors, "unknown cPanel theme name `$theme'" );
}
# Switching cPanel package
if ( $package ) {
load_packages;
if ( my $pname = $packages{ $package } || $packages{ lc( $package ) } ) {
$package = $pname;
} else {
push( @errors, "unknown package name: `$package'" );
}
}
# Applying a reseller ACL
if ( $reseller_acl ) {
load_resellers;
if ( my $aname = $reseller_acls{ $reseller_acl } || $reseller_acls{ lc( $reseller_acl ) } ) {
$reseller_acl = $aname;
} else {
push( @errors, "unknown reseller ACL name: `$reseller_acl'" );
}
}
# Changing account IP
if ( $ip ) {
load_ips;
( $ip, $subnet_mask ) = split( /[\/:\s]+/, $ip, 2 );
my $current_ip = $cp_data->{ 'IP' };
if ( $ip =~ /^(main|shared)$/i ) {
push( @errors, "could not identify the server's shared ip" ) unless ( $shared_ip );
$ip = $shared_ip;
} elsif ( $ip =~ /^(free|next)$/i ) {
unless ( @ip_pool ) {
push( @errors, "no free IPs found in the server's ip address pool" );
}
$ip = $ip_pool[ 0 ];
} elsif ( $ip !~ /$ip_rx/ ) {
push( @errors, "`$ip' is not a valid IP address" );
} elsif ( !exists $ip_addresses{ $ip } ) {
if ( $subnet_mask ) {
if ( $subnet_mask =~ /^(\d+)$/ ) {
my $prefix = $1 + 0;
if ( $prefix < 32 ) {
push( @errors, "invalid subnet prefix value: `$prefix' (must be between 0 and 32)" );
} else {
$subnet_mask = ip_bintoip( ip_get_mask( $prefix, 4 ), 4 );
$add_ip = 1;
}
} elsif ( $subnet_mask =~ /$ip_rx/ && ip_iptobin($subnet_mask, 4) =~ /$netmask_rx/ ) {
$add_ip = 1;
} else {
push @errors, "invalid subnet mask provided: `$subnet_mask'";
}
} else {
push(
@errors,
tidy qq(
| `$ip' does not seem to configured on the server.
| To add it to the server, specify a subnet mask.
)
);
}
}
if ( $add_ip ) {
# TODO: ping the IP to check if it's assigned somewhere already
}
if ( $current_ip eq $ip ) {
push( @warnings, "$user is already assigned to $ip, so it will not be changed" );
$ip = undef;
} else {
# this is a legitimate IP change
if ( $ip eq $shared_ip ) {
# check if the account has SSL installed. If it does, switching to the
# shared IP will overwrite the shared SSL. Don't do it
load_ssl;
for my $dom ( @user_domains ) {
if ( exists $ssl_domains{ $dom } ) {
push( @errors, "$user's domain `$dom' has an SSL certificate installed. Switching to the shared IP will overwrite the shared SSL certificate." );
$ip = undef;
last;
}
}
} else {
# make sure no one else already has this IP
if ( my $owner = $used_ips{ $ip } ) {
my $uname = who_owns( $owner ) || 'unknown';
push( @errors, "`$ip' is already assigned to $owner ($uname)" );
}
}
}
}
if ( %privilege_changes ) {
for my $priv_name ( keys %privilege_changes ) {
exists $PRIVILEGE_NAMES{ $priv_name } or
die "invalid privilege name: `$priv_name'";
}
}
if ( $on_shared ) {
if ( $reseller eq 'on' ) {
push( @errors, "do not apply reseller privileges to a user on a shared server" );
}
if ( $account_params{ shell } eq '1' and $server_name !~ /^edge/i ) {
push( @errors, "do not grant shell access to users on a shared server" );
}
if ( $account_params{ cgi } eq '0' ) {
push( @warnings, "you are stripping CGI access from `$user'" );
}
}
for ( @warnings ) { print STDERR c( "- $_\n", 'yellow' ); }
if ( @errors ) {
if ( $force ) {
for ( @errors ) { print STDERR c( "- $_\n", 'yellow' ); }
} else {
print STDERR c(
"Cannot make the requested modification due to the following errors:\n",
'red'
);
for ( @errors ) { print STDERR c( "- $_\n", 'yellow' ); }
print STDERR c(
"If you really want to apply these changes, specify the --force flag\n",
'red'
);
exit scalar( @errors );
}
}
####################################################################################################
###################################### WHM API Call Execution ######################################
####################################################################################################
if ( $add_ip ) {
api_call( 'addip', ip => $ip, netmask => $subnet_mask );
}
if ( $package ) {
api_call( 'changepackage', user => $user, pkg => $package );
}
if ( $password ) {
api_call( 'passwd', user => $user, pass => $password, db_pass_update => 1 );
}
if ( $ip ) {
api_call( 'setsiteip', user => $user, ip => $ip );
}
if ( defined $bandwidth ) {
api_call( 'limitbw', user => $user, bwlimit => "$bandwidth" );
}
if ( defined $quota ) {
api_call( 'editquota', user => $user, quota => "$quota" );
}
if ( $reseller ) {
my %reseller_arguments = ( user => $user );
if ( $reseller eq 'on' ) {
$reseller_arguments{ makeowner } = $reseller_owns_self if defined $reseller_owns_self;
api_call( 'setupreseller', %reseller_arguments );
} elsif ( $reseller eq 'off' ) {
api_call( 'unsetupreseller', %reseller_arguments );
}
}
if ( $reseller_acl ) {
api_call( 'setacls', reseller => $user, acllist => $reseller_acl );
}
if ( @nameservers ) {
my $ns_spec = join( ',', @nameservers );
api_call( 'setresellernameservers', user => $user, nameservers => $ns_spec );
}
if ( %privilege_changes ) {
load_resellers( 1 ); # force reload of reseller privileges, since they may have changed
my %privs = %{ $resellers{ $user } };
while ( my ( $priv_name, $enabled ) = each( %privs ) ) {
if ( $enabled ) {
$privs{ $priv_name } = 1;
} else {
delete $privs{ $priv_name };
}
}
my %params = ( reseller => $user );
for my $priv_name ( keys %privs ) {
$params{ "acl-$priv_name" } = "1";
}
api_call( 'setacls', %params );
}
if ( %account_params ) {
api_call( 'modifyacct', %account_params, user => $user );
}
if ( $account_summary ) {
my $report = api_call( 'accountsummary', user => $user, -direct => 1 );
my $summary = $report->{ acct };
$summary = $summary->[ 0 ] if $summary;
if ( $summary ) {
my @basic_properties = qw( domain email owner ip theme shell );
my @rows = ( $summary->{ user } );
for my $prop ( @basic_properties ) {
my $value = $summary->{ $prop };
push( @rows, [ $prop, $value ] );
}
my @plan_properties = qw( plan maxaddons maxparked maxsub maxsql maxpop maxftp );
push( @rows, "Package" );
for my $prop ( @plan_properties ) {
my $value = $summary->{ $prop };
push( @rows, [ $prop, $value ] );
}
my @disk_properties = qw( partition disklimit diskused );
push( @rows, "Disk" );
for my $prop ( @disk_properties ) {
my $value = $summary->{ $prop };
push( @rows, [ $prop, $value ] );
}
print_table( @rows );
}
}
exit( !$api_success );
Zerion Mini Shell 1.0