From aedfc6b887df2f82c957fbe7318e9b16ef1276e9 Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Mon, 19 Mar 2018 21:13:17 +0100 Subject: [PATCH] work in progress pluggable backup providers for https://github.com/opnsense/core/pull/2251 Define a basic interface for backup providers and try to move our GoogleDrive backup in the process. The basic idea is there, now we need to attach this to the current user interface (don't want to rework the legacy backup/restore page now) and move the last pieces for the google backup feature over to this. Next we should be able to iterate over our providers and request a backup for the ones that are activated. The responisibilty for what to backup lies within the provider at the moment, to keep things compatible and simple for the caller. --- .../library/OPNsense/Backup/BackupFactory.php | 81 +++++++ .../mvc/app/library/OPNsense/Backup/Base.php | 92 +++++++ .../app/library/OPNsense/Backup/GDrive.php | 225 ++++++++++++++++++ .../OPNsense/Backup/IBackupProvider.php | 69 ++++++ 4 files changed, 467 insertions(+) create mode 100644 src/opnsense/mvc/app/library/OPNsense/Backup/BackupFactory.php create mode 100644 src/opnsense/mvc/app/library/OPNsense/Backup/Base.php create mode 100644 src/opnsense/mvc/app/library/OPNsense/Backup/GDrive.php create mode 100644 src/opnsense/mvc/app/library/OPNsense/Backup/IBackupProvider.php diff --git a/src/opnsense/mvc/app/library/OPNsense/Backup/BackupFactory.php b/src/opnsense/mvc/app/library/OPNsense/Backup/BackupFactory.php new file mode 100644 index 000000000..8d3ffddcc --- /dev/null +++ b/src/opnsense/mvc/app/library/OPNsense/Backup/BackupFactory.php @@ -0,0 +1,81 @@ +implementsInterface('OPNsense\\Backup\\IBackupProvider') + && !$reflClass->isInterface()) { + $providers[$classname] = array( + "class" => "{$vendor}\\{$module}\\{$classname}", + "handle" => $reflClass->newInstance() + ); + } + } catch (\ReflectionException $e) { + null; // skip when unable to parse + } + } + return $providers; + } + + /** + * return a specific provider by class name (without namespace) + * @param string $className without namespace + * @return mixed|null + */ + public function getProvider($className) + { + $providers = $this->listProviders(); + if (!empty($providers[$className])) { + return $providers[$className]; + } else { + return null; + } + } +} \ No newline at end of file diff --git a/src/opnsense/mvc/app/library/OPNsense/Backup/Base.php b/src/opnsense/mvc/app/library/OPNsense/Backup/Base.php new file mode 100644 index 000000000..046cb69fd --- /dev/null +++ b/src/opnsense/mvc/app/library/OPNsense/Backup/Base.php @@ -0,0 +1,92 @@ + "GDriveEnabled", + "type" => "checkbox", + "label" => gettext("Enable") + ); + $fields[] = array( + "name" => "GDriveEmail", + "type" => "text", + "label" => gettext("Email Address") + ); + $fields[] = array( + "name" => "GDriveP12file", + "type" => "file", + "label" => gettext("P12 key (not loaded)") + ); + $fields[] = array( + "name" => "GDriveFolderID", + "type" => "text", + "label" => gettext("Folder ID") + ); + $fields[] = array( + "name" => "GDrivePrefixHostname", + "type" => "text", + "label" => gettext("Prefix hostname to backupfile") + ); + $fields[] = array( + "name" => "GDriveBackupCount", + "type" => "text", + "label" => gettext("Backup Count") + ); + $fields[] = array( + "name" => "GDrivePassword", + "type" => "password", + "label" => gettext("Password") + ); + $fields[] = array( + "name" => "GDrivePasswordConfirm", + "type" => "password", + "label" => gettext("Confirm") + ); + + return $fields; + } + + /** + * backup provider name + * @return string user friendly name + */ + public function getName() + { + return gettext("Google Drive"); + } + + /** + * validate and set configuration + * @param array $conf configuration array + * @return array of validation errors + */ + public function setConfiguration($conf) + { + // TODO: Implement setConfiguration() method. + } + + /** + * @return array filelist + */ + public function backup() + { + $cnf = Config::getInstance(); + if ($cnf->isValid()) { + $config = $cnf->object(); + if (isset($config->system->remotebackup) && isset($config->system->remotebackup->GDriveEnabled) + && !empty($config->system->remotebackup->GDriveEnabled)) { + if (!empty($config->system->remotebackup->GDrivePrefixHostname)) { + $fileprefix = (string)$config->system->hostname . "." . (string)$config->system->domain . "-"; + } else { + $fileprefix = "config-"; + } + try { + $client = new Google\API\Drive(); + $client->login($config->system->remotebackup->GDriveEmail->__toString(), + $config->system->remotebackup->GDriveP12key->__toString()); + } catch (Exception $e) { + log_error("error connecting to Google Drive"); + return array(); + } + + // backup source data to local strings (plain/encrypted) + $confdata = file_get_contents('/conf/config.xml'); + $confdata_enc = chunk_split( + $this->encrypt($confdata, $config->system->remotebackup->GDrivePassword->__toString()) + ); + + // read filelist ({prefix}*.xml) + try { + $files = $client->listFiles($config->system->remotebackup->GDriveFolderID->__toString()); + } catch (Exception $e) { + log_error("error while fetching filelist from Google Drive"); + return array(); + } + + $configfiles = array(); + foreach ($files as $file) { + if (fnmatch("{$fileprefix}*.xml", $file['title'])) { + $configfiles[$file['title']] = $file; + } + } + krsort($configfiles); + + + // backup new file if changed (or if first in backup) + $target_filename = $fileprefix . time() . ".xml"; + if (count($configfiles) > 1) { + // compare last backup with current, only save new + try { + $bck_data_enc = $client->download($configfiles[array_keys($configfiles)[0]]); + $bck_data = $this->decrypt($bck_data_enc, + $config->system->remotebackup->GDrivePassword->__toString()); + if ($bck_data == $confdata) { + $target_filename = null; + } + } catch (Exception $e) { + log_error("unable to download " . $configfiles[array_keys($configfiles)[0]]->description . " from Google Drive (" . $e . ")"); + } + } + if (!is_null($target_filename)) { + log_error("backup configuration as " . $target_filename); + try { + $configfiles[$target_filename] = $client->upload($config->system->remotebackup->GDriveFolderID->__toString(), $target_filename, $confdata_enc); + } catch (Exception $e) { + log_error("unable to upload " . $target_filename . " to Google Drive (" . $e . ")"); + return array(); + } + + krsort($configfiles); + } + + // cleanup old files + if (isset($config->system->remotebackup->GDriveBackupCount) && is_numeric($config->system->remotebackup->GDriveBackupCount->__toString())) { + $fcount = 0; + foreach ($configfiles as $filename => $file) { + if ($fcount >= $config->system->remotebackup->GDriveBackupCount->__toString()) { + log_error("remove " . $filename . " from Google Drive"); + try { + $client->delete($file); + } catch (Google_Service_Exception $e) { + log_error("unable to remove " . $filename . " from Google Drive"); + } + } + $fcount++; + } + } + + // return filelist + return $configfiles; + } + } + + // not configured / issue, return empty list + return array(); + } + + /** + * Is this provider enabled + * @return boolean enabled status + */ + public function isEnabled() + { + $cnf = Config::getInstance(); + if ($cnf->isValid()) { + $config =$cnf->object(); + return isset($config->system->remotebackup) && isset($config->system->remotebackup->GDriveEnabled) + && !empty($config->system->remotebackup->GDriveEnabled); + } + return false; + } +} \ No newline at end of file diff --git a/src/opnsense/mvc/app/library/OPNsense/Backup/IBackupProvider.php b/src/opnsense/mvc/app/library/OPNsense/Backup/IBackupProvider.php new file mode 100644 index 000000000..68bf7a214 --- /dev/null +++ b/src/opnsense/mvc/app/library/OPNsense/Backup/IBackupProvider.php @@ -0,0 +1,69 @@ +