. * */ defined('APP_ROOT') || die(_('No es posible acceder directamente a este archivo')); define('IS_INSTALLER', 1); /** * Extender la clase Exception para mostrar ayuda en los mensajes */ class InstallerException extends Exception { private $type; private $hint; public function __construct($type, $message, $hint, $code = 0, Exception $previous = null) { $this->type = $type; $this->hint = $hint; parent::__construct($message, $code, $previous); } public function __toString() { return __CLASS__ . ": [{$this->code}]: {$this->message} ({$this->hint})\n"; } public function getHint() { return $this->hint; } public function getType() { return $this->type; } } /** * Esta clase es la encargada de instalar sysPass. */ class SP_Installer { private static $dbuser; private static $dbname; private static $dbhost; private static $dbc; // Database connection private static $username; private static $password; private static $masterPassword; private static $isHostingMode; /** * Iniciar instalación. * * @param array $options datos de instalación * @return array resultado del proceso */ public static function install($options) { $error = array(); if (empty($options['adminlogin'])) { $error[] = array( 'type' => 'critical', 'description' => _('Indicar nombre de usuario admin'), 'hint' => _('Usuario admin para acceso a la aplicación')); } if (empty($options['adminpass'])) { $error[] = array( 'type' => 'critical', 'description' => _('Indicar la clave de admin'), 'hint' => _('Clave del usuario admin de la aplicación')); } if (empty($options['masterpassword'])) { $error[] = array( 'type' => 'critical', 'description' => _('Indicar la clave maestra'), 'hint' => _('Clave maestra para encriptar las claves')); } if (strlen($options['masterpassword']) < 11) { $error[] = array( 'type' => 'critical', 'description' => _('Clave maestra muy corta'), 'hint' => _('La longitud de la clave maestra ha de ser mayor de 11 caracteres')); } if (empty($options['dbuser'])) { $error[] = array( 'type' => 'critical', 'description' => _('Indicar el usuario de la BBDD'), 'hint' => _('Usuario con permisos de administrador de la Base de Datos')); } if (empty($options['dbpass'])) { $error[] = array( 'type' => 'critical', 'description' => _('Indicar la clave de la BBDD'), 'hint' => _('Clave del usuario administrador de la Base de Datos')); } if (empty($options['dbname'])) { $error[] = array( 'type' => 'critical', 'description' => _('Indicar el nombre de la BBDD'), 'hint' => _('Nombre para la BBDD de la aplicación pej. syspass')); } if (substr_count($options['dbname'], '.') >= 1) { $error[] = array( 'type' => 'critical', 'description' => _('El nombre de la BBDD no puede contener "."'), 'hint' => _('Elimine los puntos del nombre de la Base de Datos')); } if (empty($options['dbhost'])) { $error[] = array( 'type' => 'critical', 'description' => _('Indicar el servidor de la BBDD'), 'hint' => _('Servidor donde se instalará la Base de Datos')); } if (count($error) == 0) { //no errors, good self::$username = htmlspecialchars_decode($options['adminlogin']); self::$password = htmlspecialchars_decode($options['adminpass']); self::$masterPassword = htmlspecialchars_decode($options['masterpassword']); self::$dbname = trim($options['dbname']); self::$dbhost = trim($options['dbhost']); //generate a random salt that is used to salt the local user passwords $salt = SP_Util::generate_random_bytes(30); SP_Config::setValue('passwordsalt', $salt); SP_Config::setValue('version', implode(SP_Util::getVersion(true))); $dbadmin = trim($options['dbuser']); $dbpass = trim($options['dbpass']); if (preg_match('/(.*):(\d{1,5})/', trim($options['dbhost']), $match)){ $dbhost = $match[1]; $dbport = $match[2]; } else { $dbhost = trim($options['dbhost']); $dbport = 3306; } self::$isHostingMode = (isset($options['hostingmode'])) ? 1 : 0; // Save DB connection info SP_Config::setValue('dbhost', $dbhost); SP_Config::setValue('dbname', self::$dbname); SP_Config::setValue('dbport', $dbport); // Set some basic configuration options SP_Config::setDefaultValues(); try { self::checkDatabaseAdmin($dbhost, $dbadmin, $dbpass, $dbport); self::setupMySQLDatabase(); self::createAdminAccount(); } catch (InstallerException $e) { $error[] = array('type' => $e->getType(), 'description' => $e->getMessage(), 'hint' => $e->getHint()); return ($error); } SP_Config::setConfigValue('version', implode(SP_Util::getVersion(true))); SP_Config::setValue('installed', 1); } return ($error); } /** * Comprobar la conexión con la BBDD. * Comprobar si la conexión con la base de datos para sysPass es posible con * los datos facilitados. * * @param string $dbhost host de conexión * @param string $dbadmin usuario de conexión * @param string $dbpass clave de conexión * @param int $dbport puerto de conexión a la BD * @throws InstallerException * @return none */ private static function checkDatabaseAdmin($dbhost, $dbadmin, $dbpass, $dbport) { self::$dbc = @new mysqli($dbhost, $dbadmin, $dbpass, '', $dbport); if (!is_object(self::$dbc) || self::$dbc->connect_errno) { error_log('MySQL Error: ' . self::$dbc->connect_error . ' (' . self::$dbc->connect_errno . ')'); throw new InstallerException('critical', _('No es posible conectar con la BD'), _('Compruebe los datos de conexión') . '
' . self::$dbc->connect_error); } } /** * Configurar la base de datos. * Esta función crea la base de datos y el usuario necesario para sysPass. * * @throws InstallerException * @return none */ private static function setupMySQLDatabase() { $oldUser = SP_Config::getValue('dbuser', false); //this should be enough to check for admin rights in mysql $query = "SELECT user " . "FROM mysql.user " . "WHERE user='" . self::$username . "' and host='" . self::$dbhost . "';"; // Hash DB connection password $dbpassword = (!self::$isHostingMode) ? md5(time() . self::$password) : self::$password; self::$dbuser = (!self::$isHostingMode) ? substr('sp_' . self::$username, 0, 16) : self::$username; if (!self::$dbc->query($query)) { if (self::$dbuser != $oldUser) { self::createDBUser($dbpassword); SP_Config::setValue('dbuser', self::$dbuser); SP_Config::setValue('dbpass', $dbpassword); } } else { if (self::$username != $oldUser) { SP_Config::setValue('dbuser', self::$dbuser); SP_Config::setValue('dbpass', $dbpassword); } } self::createMySQLDatabase($dbpassword); if (!self::checkDatabaseExist()) { self::createDBStructure(); } else { throw new InstallerException('critical' , _('La BBDD ya existe') , _('Indique una nueva Base de Datos o elimine la existente')); } // self::$dbc->close(); } /** * Crear el usuario para conectar con la base de datos. * Esta función crea el usuario para conectar con la base de datos. * Si se marca en modo hosting, no se crea el usuario. * * @param string $dbpassword clave del usuario de sysPass * @throws InstallerException * @return none */ private static function createDBUser($dbpassword) { if (self::$isHostingMode) { return; } $query = "CREATE USER '" . self::$dbuser . "'@'localhost' IDENTIFIED BY '" . $dbpassword . "'"; if (!self::$dbc->query($query)) { throw new InstallerException('critical' , _('El usuario de MySQL ya existe') . " (" . self::$dbuser . ")" , _('Indique un nuevo usuario o elimine el existente')); } } /** * Crear la base de datos. * * @param string $dbpassword clave del usuario de sysPass * @throws InstallerException * @return none */ private static function createMySQLDatabase($dbpassword) { $query = "CREATE DATABASE IF NOT EXISTS `" . self::$dbname . "`"; if (!self::$dbc->query($query)) { throw new InstallerException('critical' , _('Error al crear la BBDD') . " (" . self::$dbc->error . ")" , _('Verifique los permisos del usuario de la Base de Datos')); } if (!self::$isHostingMode) { $dbhost = (!preg_match('/(127\.0\.0\.1|localhost)/i', self::$dbhost)) ? $_SERVER['SERVER_ADDR'] : self::$dbhost; $query = "GRANT ALL PRIVILEGES ON `" . self::$dbname . "`.* TO '" . self::$dbuser . "'@'$dbhost' IDENTIFIED BY '$dbpassword';"; self::$dbc->query($query); if (!self::$dbc->query($query)) { throw new InstallerException('critical' , _('Error al establecer permisos de la BBDD') . " (" . self::$dbc->error . ")" , _('Verifique los permisos del usuario de la Base de Datos')); } } } /** * Comprobar si la base de datos indicada existe. * * @return bool */ private static function checkDatabaseExist() { $query = "SELECT COUNT(*) " . "FROM information_schema.tables " . "WHERE table_schema = '" . self::$dbname . "' " . "AND table_name = 'usrData';"; $resquery = self::$dbc->query($query); if ($resquery) { $row = $resquery->fetch_row(); } if (!$resquery || $row[0] == 0) { return false; } return true; } /** * Crear la estructura de la base de datos. * Esta función crea la estructura de la base de datos a partir del archivo dbsctructure.sql. * * @throws InstallerException * @return none */ private static function createDBStructure() { $fileName = dirname(__FILE__) . '/dbstructure.sql'; if (!file_exists($fileName)) { throw new InstallerException('critical' , _('El archivo de estructura de la BBDD no existe') , _('No es posible crear la BBDD de la aplicación. Descárguela de nuevo.')); } // Usar la base de datos de sysPass if (!self::$dbc->select_db(self::$dbname)) { throw new InstallerException('critical' , _('Error al seleccionar la BBDD') . " '" . self::$dbname . "' (" . self::$dbc->error . ")" , _('No es posible usar la Base de Datos para crear la estructura. Compruebe los permisos y que no exista.')); } // Leemos el archivo SQL para crear las tablas de la BBDD $handle = fopen($fileName, 'rb'); if ($handle) { while (!feof($handle)) { $buffer = stream_get_line($handle, 1000000, ";\n"); if (strlen(trim($buffer)) > 0) { if (!self::$dbc->query($buffer)) { // drop database on error self::$dbc->query("DROP DATABASE " . self::$dbname . ";"); throw new InstallerException('critical' , _('Error al crear la BBDD') , _('Error al crear la estructura de la Base de Datos.')); } } } } } /** * Crear el usuario admin de sysPass. * Esta función crea el grupo, perfil y usuario 'admin' para utilizar sysPass. * * @throws InstallerException * @return none */ private static function createAdminAccount() { $user = new SP_Users; // Datos del grupo SP_Groups::$groupName = "Admins"; SP_Groups::$groupDescription = "Admins"; if (!SP_Groups::addGroup()) { self::rollback(); throw new InstallerException("critical" , _('Error al crear el grupo "admin"') , _('Informe al desarrollador')); } // Establecer el id de grupo del usuario al recién creado $user->userGroupId = SP_Groups::$queryLastId; $profileProp = array("pAccView" => 1, "pAccViewPass" => 1, "pAccViewHistory" => 1, "pAccEdit" => 1, "pAccEditPass" => 1, "pAccAdd" => 1, "pAccDel" => 1, "pAccFiles" => 1, "pConfig" => 1, "pConfigMpw" => 1, "pConfigBack" => 1, "pAppMgmtCat" => 1, "pAppMgmtCust" => 1, "pUsers" => 1, "pGroups" => 1, "pProfiles" => 1, "pEventlog" => 1); SP_Profiles::$profileName = 'Admin'; if (!SP_Profiles::addProfile($profileProp)) { self::rollback(); throw new InstallerException("critical" , _('Error al crear el perfil "admin"') , _('Informe al desarrollador')); } // Establecer el id de perfil del usuario al recién creado $user->userProfileId = SP_Profiles::$queryLastId; // Datos del usuario $user->userLogin = self::$username; $user->userPass = self::$password; $user->userName = "Admin"; $user->userIsAdminApp = 1; if (!$user->addUser()) { self::rollback(); throw new InstallerException('critical' , _('Error al crear el usuario "admin"') , _('Informe al desarrollador')); } // Guardar el hash de la clave maestra SP_Config::$arrConfigValue["masterPwd"] = SP_Crypt::mkHashPassword(self::$masterPassword); SP_Config::$arrConfigValue["lastupdatempass"] = time(); SP_Config::writeConfig(true); $user->userId = $user->queryLastId; // Needed for update user's master password if (!$user->updateUserMPass(self::$masterPassword, false)) { self::rollback(); throw new InstallerException('critical' , _('Error al actualizar la clave maestra del usuario "admin"') , _('Informe al desarrollador')); } } /** * Deshacer la instalación en caso de fallo. * Esta función elimina la base de datos y el usuario de sysPass * * @return none */ private static function rollback() { self::$dbc->query("DROP DATABASE IF EXISTS " . self::$dbname . ";"); self::$dbc->query("DROP USER '" . self::$dbuser . "'@'" . self::$dbhost . "';"); self::$dbc->query("DROP USER '" . self::$dbuser . "'@'%';"); SP_Config::deleteKey('dbuser'); SP_Config::deleteKey('dbpass'); } }