<?php
/* vim: set expandtab sw=4 ts=4 sts=4: */
/**
* Custom cPanel Functions
* Provides the necessary functions to perform
* privileged MySQL actions as user.
*
* @version $Id$
* @package phpMyAdmin
*/
// This is a library of functions provided by cPanel, Inc and is distributed under the same terms as phpMyAdmin itself (GPLv2).
if ( !defined( 'PHPMYADMIN' ) ) {
exit;
}
use PhpMyAdmin\Util;
/**
* Pass MySQL action to cpmysqlwrap
*
* Core function responsible for send desired MySQL
* action to the privilege-aware cPanel binary
* cpmysqlwrap. cpmysqlwrap takes a limited set
* of corelative API calls with arguments
*
* @access private
* @param string $action The corelative API call to envoke
* @param array $args Arguements for necessary for $action
* @return array Array with 'raw_output' and 'exit_code' generated by cpmysqlwrap
*/
function _PMA_CPANEL_API_action( $action, $args = array() ) {
//bail on blank/null action
if ( empty( $action ) ) {
$err_msg = "No cPanel action to perform against MySQL.";
PMA_CPANEL_die( $err_msg );
}
// define our privilege binary
$cpmysqladmin_bin = '/usr/local/cpanel/bin';
if ( file_exists( $cpmysqladmin_bin . '/cpmysqlwrap' ) ) {
$cpmysqladmin_bin .= '/cpmysqlwrap';
}
else {
$cpmysqladmin_bin .= '/mysqlwrap';
}
$input = $action;
if ( !empty( $args ) ) {
foreach ( $args as $arg ) {
if ( $arg !== NULL && $arg !== '' ) {
$input .= " $arg";
}
}
}
//calling api action should have cleaned the input, as necessary...
// but just in case we'll escape the input string before exec'ing it
$input = escapeshellcmd( $input );
$raw_output = array();
$exit_code;
exec( "$cpmysqladmin_bin $input", $raw_output, $exit_code );
return array(
'raw_output' => $raw_output,
'exit_code' => $exit_code,
);
}// end _PMA_CPANEL_action
/**
* Invoke cPanel API function 'adddb'
*
* Wrapper function for envoking the MySQL API call which
* creates a database for the user.
* @NOTE: Due to the way cPanel enforces prefixing,
* the requested db name may be prepend with "$user_"
* and if so, the function will alter the db name.
*
* @access public
* @param string &$db_name Name of database to create.
* @return array Array with 'sql_query' equivalent sql plus 'raw_output' and 'exit_code' generated by cpmysqlwrap
*/
function PMA_CPANEL_API_adddb( &$db_name ) {
_PMA_CPANEL_validateDbName( $db_name );
$results = array();
if ( ! _PMA_CPANEL_isPriveleged() ) {
$results = _PMA_CPANEL_API_action( 'ADDDB', array($db_name) );
//check for errors
if ( $results['exit_code'] !== 0 || !empty( $results['raw_output'] ) ) {
/**
* adddb was in error
* mysqlwrap adddb should provide output on failure
* status code of 1 is a catastrophic failure
* status code of 0 may be success or failure,
* this is a happenstance of the adminbin wrapper
*/
$err_msg = ( !empty( $results['raw_output'] ) ) ? implode( "\n", $results['raw_output'] ) : "Could not perform MySQL action.";
PMA_CPANEL_die( $err_msg );
}
//double check that the db was created
$dbs = PMA_CPANEL_API_listdbs();
$db_hunt = '';
//first, get the true cpanel db name, if necessary
if ( PMA_CPANEL_API_useprefix() ) {
$cpprefix = PMA_CPANEL_getPrefix();
if ( strpos( $db_name, $cpprefix ) !== 0 ) {
$db_hunt = $cpprefix;
}
}
$db_hunt .= $db_name;
if ( empty( $dbs ) || !is_array( $dbs ) || !in_array( $db_hunt, $dbs ) ) {
$err_msg = "Database $db_hunt not created.";
PMA_CPANEL_die( $err_msg );
}
//set the new db name to what cpanel created
$db_name = $db_hunt;
//populate faux query statement for display purposes
// the create action is actually performed by mysqladmin
$results['sql_query'] = "CREATE DATABASE `$db_hunt`;\n";
}
else{
// running via WHM, use PMA code
$local_query = 'CREATE DATABASE ' . Util::backquote( $db_name ) . ';';
$results['sql_query'] = $local_query."\n";
// save the original db name because Tracker.class.php which
// may be called under PMA_DBI_query
// for some statements, one of which being CREATE DATABASE
$original_db = $db_name;
if ( $GLOBALS['dbi']->query( $local_query ) ) {
$results['raw_output'] = '';
$results['exit_code'] = 0;
}
else {
$results['raw_output'] = 'phpMyAdmin could not CREATE database ' . $original_db;
$results['exit_code'] = 1; //we should be here, but for completion's sake
}
$db = $original_db;
unset( $original_db );
}
return $results;
}//end PMA_CPANEL_API_adddb
/**
* Envoke cPanel API function 'deldb'
*
* Wrapper function for envoking the MySQL API call which
* drops a database for the user.
*
* @access public
* @param string $db_name Name of database to dropped.
* @return array Array with 'sql_query' equivalent sql plus 'raw_output' and 'exit_code' generated by cpmysqlwrap
*/
function PMA_CPANEL_API_deldb( $db_name ) {
_PMA_CPANEL_validateDbName( $db_name );
$results = array();
if ( ! _PMA_CPANEL_isPriveleged() ) {
$results = _PMA_CPANEL_API_action( 'DELDB', array( $db_name ) );
//check for errors
$expected_msg = 'Database "' . $db_name . '" dropped';
if ( $results['exit_code'] !== 0
|| ( !empty( $results['raw_output'] ) && $results['raw_output'][0] !== $expected_msg ) ) {
/**
* adddb was in error
* mysqlwrap adddb should provide output on failure
* status code of 1 is a catastrophic failure
* status code of 0 may be success or failure,
* this is a happenstance of the adminbin wrapper
* with DELDB, specifically, we get an info message
*/
$err_msg = ( !empty( $results['raw_output']) ) ? implode( "\n", $results['raw_output'] ) : "Could not perform MySQL action.";
PMA_CPANEL_die( $err_msg );
}
//double check that the db was dropped
$dbs = PMA_CPANEL_API_listdbs();
if ( is_array( $dbs ) && in_array( $db_name, $dbs ) ) {
$err_msg = "Database $db_name not dropped.";
PMA_CPANEL_die( $err_msg );
}
//populate faux query statement for display purposes
// the drop action is actually performed by mysqladmin
$results['sql_query'] = "DELETE FROM mysql.db WHERE Db='$db_name';\n"
."DELETE FROM mysql.host WHERE Db='$db_name';\n"
."DELETE FROM mysql.tables_priv WHERE Db='$db_name';\n"
."DELETE FROM mysql.columns_priv WHERE Db='$db_name';\n"
."DROP DATABASE `$db_name`;\n";
}
else {
// running via WHM, use PMA code
// if someday the RENAME DATABASE reappears, do not DROP
$local_query = 'DROP DATABASE ' . Util::backquote( $db_name ) . ';';
$results['sql_query'] = $local_query . "\n";
if ( $GLOBALS['dbi']->query( $local_query ) ) {
$results['raw_output'] = '';
$results['exit_code'] = 0;
}
else {
$results['raw_output'] = 'phpMyAdmin could not DROP database ' . $db_name;
$results['exit_code'] = 1; //we should be here, but for completion's sake
}
}
return $results;
}//end PMA_CPANEL_API_deldb
/**
* Envoke cPanel API function 'listdbs'
*
* Wrapper function for envoking the MySQL API call which
* lists all databases for the user.
*
* @access public
* @return array Array of db names
*/
function PMA_CPANEL_API_listdbs() {
$results = _PMA_CPANEL_API_action( 'LISTDBS' );
return $results['raw_output'];
}//end PMA_CPANEL_API_listdbs
/**
* Envoke cPanel API function 'useprefix'
*
* Wrapper function for envoking the MySQL API call which
* returns the state of DB Prefixing on the server.
*
* @access public
* @return boolean TRUE for prefixing "on" (the default), FALSE for off
* or if effective user is 'cpanelphpmyadmin' because this will cause
* the underlying USEPREFIX adminbin to fail with an error of not finding
* info for user 'cpanelphpmyadmin'
*/
function PMA_CPANEL_API_useprefix() {
$_userinfo = posix_getpwuid( posix_getuid() );
if ( $_userinfo['name'] == 'cpanelphpmyadmin' ) {
return false;
}
$results = _PMA_CPANEL_API_action( 'USEPREFIX' );
$state = ( count( $results['raw_output'] ) ) ? $results['raw_output'][0] : 1;
return (bool) $state;
}//end PMA_CPANEL_API_useprefix
/**
* Wrapper function for using PMA to set collation of a database
*
* Wrapper function that will alter a database's collation. Internally
* it uses PMA_DBI_query. This is a utility function that can be called
* arbitrarily and provides easy access to the sql statement for UI rendering.
*
* @access public
* @param string $db Database to change.
* @param string $db_collation The collation to set (value based on user select in PMA)
* @return array Array with 'sql_query' equivalent sql plus 'pma_output' generated by PMA_DBI_query()
*/
function PMA_CPANEL_SQL_setDbCollation( $db, $db_collation ) {
_PMA_CPANEL_validateDbName( $db );
$results['sql_query'] = 'ALTER DATABASE ' . Util::backquote( $db ) . ' DEFAULT' . Util::getCharsetQueryPart( $db_collation );
$results['pma_result'] = $GLOBALS['dbi']->query( $results['sql_query'] );
$results['sql_query'] .= ";\n";
return $results;
}//end PMA_CPANEL_SQL_setDbCollation
/**
* Utility function to determine proper user prefix
*
* Returns the proper prefix for the effective user.
* @NOTE: All cPanel users will have a "prefix" though
* its use is determined based on the state of DB Prefixing.
* if the PMA is being run via WHM, there is no prefix since
* the effective user is a system user, cpanelphpmyadmin
*
* @access public
* @return string Effective user prefix
*/
function PMA_CPANEL_getPrefix() {
$username = (posix_getpwuid ( posix_getuid() ))['name'];
if ($username == 'cpanelphpmyadmin') {
return '';
}
$results = _PMA_CPANEL_API_action('GETPREFIXLENGTH');
$prefix_length = $results['raw_output'][0];
if (strlen($username) > $prefix_length) {
$username = substr($username, 0, $prefix_length);
}
return $username . '_';
}//end PMA_CPANEL_getPrefix
/**
* Utility function to determine whether truncation should be applied.
*
* Returns a boolean that indicates whether truncation to 8 characters should be
* performed.
*
* @access public
* @return boolean TRUE if truncation should be performed, FALSE otherwise.
*/
function PMA_CPANEL_shouldTruncate() {
$results = _PMA_CPANEL_API_action( 'VERSION' );
return ($results['raw_output'][0] + 0) < 10.0;
}
/**
* Displays a cPanel related error message in the right frame.
*
* Override the 'strMySQLSaid' global with value 'cPanel said:' and
* internally can Util::mysqlDie which will render the page immediately
*
* @access public
* @param string Error message to print
* @return void
*/
function PMA_CPANEL_die( $error_message = '' ) {
$GLOBALS['strMySQLSaid'] = 'cPanel said: ';
Util::mysqlDie( $error_message );
} //end PMA_CPANEL_die
/**
* Check if PMA is being executed by a WHM/cPanel "super" user
*
* @access private
* @return bool
*/
function _PMA_CPANEL_isPriveleged() {
global $cfg;
$pma_user = $cfg['Server']['user'];
$eff_user = posix_getpwuid( posix_getuid() );
if ( $pma_user == 'root' && $eff_user['name'] == 'cpanelphpmyadmin' ) {
return TRUE;
}
return FALSE;
}//end of _PMA_CPANEL_isPriveleged
/**
* Append prefix to DB name if ( PMA_CPANEL_API_useprefix() ),
* making sure not to add it if it exists.
*/
function _PMA_CPANEL_addPrefixIfNeeded( $db_name ) {
$db_hunt = '';
//first, get the true cpanel db name, if necessary
if ( PMA_CPANEL_API_useprefix() ) {
$cpprefix = PMA_CPANEL_getPrefix();
if ( strpos( $db_name, $cpprefix ) !== 0 ) {
$db_hunt = $cpprefix;
}
}
$db_hunt .= $db_name;
return $db_hunt;
}
/**
* Check if user supplied database name is valid
*
* In cPanel, we allow alphanumerics (a-z,A-Z,0-9), the underscore (_) and the
* hyphen (-). All cPanel action function in this interface should call this
* function before attempting to perform a database operation. The function
* will bail the envoking action by using PMA_CPANEL_die so that the proper
* error message is register for rendering in the PMA UI.
*
* @access private
* @param string $db_name The database name (from user input) to check
* @return mixed TRUE if database name is with [a-zA-Z0-9_-], safe exit if not
*/
function _PMA_CPANEL_validateDbName( $db_name ) {
$input = str_replace( array('-','_'), '', $db_name );
if ( ctype_alnum( $input ) ) {
return true;
}
$err_msg =
"\"$db_name\" is not a valid database name.\n"
."Only alphanumeric, '-' and '_' characters are allowed.\n";
PMA_CPANEL_die( $err_msg );
}//end of _PMA_CPANEL_validateDbName
?>