Beispiel #1
0
 /**
  * This interrupts requests if all else fails.
  * @param string[] ...$args
  * @return void
  * @throws CustomPageNotFoundException
  */
 public function routeNotFound(...$args)
 {
     if (!\is1DArray($args)) {
         throw new CustomPageNotFoundException(\__('Invalid arguments'));
     }
     $dirs = $args;
     $file = \array_pop($dirs);
     // First: Do we have a custom page at this endpoint?
     try {
         if ($this->serveCustomPage($file, $dirs)) {
             return;
         }
     } catch (CustomPageNotFoundException $ex) {
         $this->log('Custom page not found', LogLevel::INFO, ['cabin' => $this->cabin, 'dirs' => $dirs, 'exception' => \Airship\throwableToArray($ex), 'file' => $file]);
     }
     // Second: Is there a redirect at this endpoint?
     try {
         $path = \implode('/', $args);
         if ($this->pages->serveRedirect($path)) {
             return;
         }
     } catch (RedirectException $ex) {
         $this->log('Redirect missed', LogLevel::INFO);
     }
     \http_response_code(404);
     // Finally: Return a 4o4
     $this->lens('404');
     return;
 }
Beispiel #2
0
 /**
  * Process automatic updates:
  *
  * 1. Check if a new update is available.
  * 2. Download the upload file, store in a temporary file.
  * 3. Verify the signature (via Halite).
  * 4. Verify the update is recorded in Keyggdrasil.
  * 5. If all is well, run the update script.
  */
 public function autoUpdate()
 {
     $state = State::instance();
     try {
         /**
          * @var UpdateInfo[]
          */
         $updateInfoArray = $this->updateCheck($state->universal['airship']['trusted-supplier'], $this->name, \AIRSHIP_VERSION, 'airship_version');
         /**
          * Let's iterate through every possible available update,
          * until we find the desired version.
          */
         foreach ($updateInfoArray as $updateInfo) {
             if (!$this->checkVersionSettings($updateInfo, \AIRSHIP_VERSION)) {
                 // This is not the update we are looking for.
                 $this->log('Skipping update', LogLevel::DEBUG, ['info' => $updateInfo->getResponse(), 'new_version' => $updateInfo->getVersion(), 'current_version' => \AIRSHIP_VERSION]);
                 continue;
             }
             /**
              * @var UpdateFile
              */
             $updateFile = $this->downloadUpdateFile($updateInfo, 'airship_download');
             if ($this->bypassSecurityAndJustInstall) {
                 // I'm sorry, Dave. I'm afraid I can't do that.
                 $this->log('Core update verification cannot be bypassed', LogLevel::ERROR);
                 self::$continuumLogger->store(LogLevel::ALERT, 'CMS Airship core update - security bypass ignored.', $this->getLogContext($updateInfo, $updateFile));
             }
             /**
              * Don't proceed unless we've verified the signatures
              */
             if ($this->verifyUpdateSignature($updateInfo, $updateFile)) {
                 if ($this->checkKeyggdrasil($updateInfo, $updateFile)) {
                     $this->install($updateInfo, $updateFile);
                 } else {
                     $this->log('Keyggdrasil check failed for Airship core update', LogLevel::ALERT);
                     self::$continuumLogger->store(LogLevel::ALERT, 'CMS Airship core update failed -- checksum not registered in Keyggdrasil', $this->getLogContext($updateInfo, $updateFile));
                 }
             } else {
                 $this->log('Invalid signature for this Airship core update', LogLevel::ALERT);
                 self::$continuumLogger->store(LogLevel::ALERT, 'CMS Airship core update failed -- invalid signature', $this->getLogContext($updateInfo, $updateFile));
             }
         }
     } catch (NoAPIResponse $ex) {
         // We should log this.
         $this->log('Automatic update failure: NO API Response.', LogLevel::ERROR, \Airship\throwableToArray($ex));
         self::$continuumLogger->store(LogLevel::ALERT, 'CMS Airship core update failed -- no API Response', ['action' => 'UPDATE', 'name' => $this->name, 'supplier' => $this->supplier->getName(), 'type' => $this->type]);
     }
 }
Beispiel #3
0
 /**
  * Upload files
  *
  * @param int $directoryId
  * @param string $cabin
  * @return array
  */
 protected function uploadFiles($directoryId = null, string $cabin = '') : array
 {
     $results = [];
     $newFiles = $this->files->isolateFiles($_FILES['new_files']);
     if (empty($newFiles)) {
         return ['status' => 'ERROR', 'message' => 'No files were uploaded.'];
     }
     foreach ($newFiles as $file) {
         try {
             $results[] = $this->files->processUpload($directoryId, $cabin, $file, $this->attribution);
         } catch (UploadError $ex) {
             $this->log('File upload failed', LogLevel::ERROR, \Airship\throwableToArray($ex));
         }
     }
     return ['status' => 'SUCCESS', 'message' => 'Upload successful'];
 }
Beispiel #4
0
                echo "\n", \str_repeat('#', 80), "\n";
                echo "PREVIOUS EXCEPTION (", $n, "): ", \get_class($e), "\n\n", $e->getMessage(), "\n\n", $e->getCode(), "\n\n", $e->getTraceAsString();
                ++$n;
                if (!$e) {
                    exit(255);
                }
            } elseif ($e instanceof \Error) {
                echo "\n", \str_repeat('#', 80), "\n";
                echo "PREVIOUS ERROR (", $n, "): ", \get_class($e), "\n\n", $e->getMessage(), "\n\n", $e->getCode(), "\n\n", $e->getTraceAsString();
                ++$n;
                if (!$e) {
                    exit(255);
                }
            } else {
                break;
            }
        }
        exit(255);
    }
    // This is just for benchmarking purposes:
    echo '<!-- Load time: ' . \round(\microtime(true) - $start, 5) . ' s -->';
} else {
    try {
        $autoPilot->route();
    } catch (\Throwable $e) {
        $state->logger->log(LogLevel::ERROR, $e->getMessage(), \Airship\throwableToArray($e));
        \http_response_code(500);
        echo \file_get_contents(ROOT . '/error_pages/uncaught-exception.html');
        exit(1);
    }
}
Beispiel #5
0
 /**
  * Get path information by a page ID
  *
  * @param int $pageId
  * @param string $cabin
  * @return array string[3] (cabin, directory, page)
  * @throws CustomPageNotFoundException
  */
 public function getPathByPageId(int $pageId, string $cabin = '') : array
 {
     $page = $this->db->row('SELECT url, directory FROM airship_custom_page WHERE pageid = ?', $pageId);
     if (empty($page)) {
         throw new CustomPageNotFoundException(\trk('errors.pages.page_does_not_exist'));
     }
     if (empty($page['directory'])) {
         return [];
     }
     try {
         $path = \implode('/', $this->getPathFromDirectoryId((int) $page['directory'], $cabin));
         return [$cabin, \trim($path, '/'), $page['url']];
     } catch (CustomPageNotFoundException $ex) {
         $this->log('Custom directory not found', LogLevel::ALERT, ['pageid' => $pageId, 'url' => $page['url'], 'directory' => $page['directory'], 'cabin' => $cabin, 'exception' => \Airship\throwableToArray($ex)]);
         throw $ex;
     }
 }
Beispiel #6
0
 /**
  * This serves the fallback route, if it's defined.
  *
  * The fallback route handles:
  *
  * - Custom pages (if any exist), or
  * - Redirects
  *
  * @return mixed
  */
 protected function serveFallback()
 {
     // If we're still here, let's try the fallback handler
     if (isset($this->cabin['data']['route_fallback'])) {
         \preg_match('#^/?' . self::$patternPrefix . '/(.*)$#', $_SERVER['REQUEST_URI'], $args);
         try {
             return $this->serve($this->cabin['data']['route_fallback'], \explode('/', $args[1] ?? ''));
         } catch (FallbackLoop $e) {
             $state = State::instance();
             $state->logger->error('Missing route definition', ['exception' => \Airship\throwableToArray($e)]);
             // We only catch this one
         }
     }
     // If we don't have a fallback handler defined, just give a 404 status and kill the script.
     \http_response_code(404);
     exit(255);
 }
Beispiel #7
0
 /**
  * Get metadata about the package we're installing.
  *
  * @param string $minVersion
  * @return array
  * @throws NoAPIResponse
  */
 public function getPackageData(string $minVersion = '') : array
 {
     $channelsConfigured = $this->supplier->getChannels();
     if (empty($channelsConfigured)) {
         throw new NoAPIResponse(\trk('errors.hail.no_channel_configured', $this->supplier->getName()));
     }
     /**
      * HTTP POST arguments
      */
     $args = ['type' => $this->type, 'supplier' => $this->supplier->getName(), 'package' => $this->package];
     if (!empty($minVersion)) {
         $args['minimum'] = $minVersion;
     }
     /**
      * Let's try each of the channels this supplier
      * belongs to. This should in most cases only
      * run once.
      */
     foreach ($channelsConfigured as $channel) {
         $chan = $this->getChannel($channel);
         $publicKey = $chan->getPublicKey();
         // Iterate through all the available Channel URLs.
         // If the channel supports Tor, and we have tor-only
         // mode enabled, it will prioritize those URLs.
         foreach ($chan->getAllURLs() as $ch) {
             try {
                 $result = $this->hail->postSignedJSON($ch . API::get('version'), $publicKey, $args);
                 // Add the channel to this data...
                 $result['channel'] = $ch;
                 $result['minimum'] = (string) ($minVersion ?? '0.0.0');
                 return $result;
             } catch (TransferException $ex) {
                 $this->log('This channel URL did not respond.', LogLevel::INFO, ['channel' => $channel, 'url' => $ch, 'exception' => \Airship\throwableToArray($ex)]);
             } catch (SignatureFailed $ex) {
                 $this->log('Channel signature validation failed', LogLevel::WARNING, ['channel' => $channel, 'url' => $ch, 'exception' => \Airship\throwableToArray($ex)]);
             }
             // If we didn't return a result, we'll continue onto the next URL
         }
     }
     throw new NoAPIResponse(\trk('errors.hail.no_channel_responded'));
 }
Beispiel #8
0
<?php

declare (strict_types=1);
use Airship\Engine\{Gears, Hail, State};
/**
 * @global State $state
 * @global Hail $hail
 */
// Always check for changes to channel keys before initiating update
require_once __DIR__ . '/channel.php';
/**
 * Initialize the automatic updater service
 * @var \Airship\Engine\Continuum
 */
$autoUpdater = Gears::get('AutoUpdater', $hail);
if (IDE_HACKS) {
    // Just for the sake of auto-completion:
    $autoUpdater = new \Airship\Engine\Continuum($hail);
}
$state->logger->info('Automatic update started');
try {
    $autoUpdater->doUpdateCheck();
} catch (\Throwable $ex) {
    $state->logger->critical('Tree update failed: ' . \get_class($ex), \Airship\throwableToArray($ex));
    exit(255);
}
$state->logger->info('Automatic update concluded');
\Airship\clear_cache();
exit(0);
Beispiel #9
0
 /**
  * Handle user authentication
  *
  * @param array $post
  */
 protected function processLogin(array $post = [])
 {
     $state = State::instance();
     if (empty($post['username']) || empty($post['passphrase'])) {
         $this->lens('login', ['post_response' => ['message' => \__('Please fill out the form entirely'), 'status' => 'error']]);
     }
     $airBrake = Gears::get('AirBrake');
     if (IDE_HACKS) {
         $airBrake = new AirBrake();
     }
     if ($airBrake->failFast($post['username'], $_SERVER['REMOTE_ADDR'])) {
         $this->lens('login', ['post_response' => ['message' => \__('You are doing that too fast. Please wait a few seconds and try again.'), 'status' => 'error']]);
     } elseif (!$airBrake->getFastExit()) {
         $delay = $airBrake->getDelay($post['username'], $_SERVER['REMOTE_ADDR']);
         if ($delay > 0) {
             \usleep($delay * 1000);
         }
     }
     try {
         $userID = $this->airship_auth->login($post['username'], new HiddenString($post['passphrase']));
     } catch (InvalidMessage $e) {
         $this->log('InvalidMessage Exception on Login; probable cause: password column was corrupted', LogLevel::CRITICAL, ['exception' => \Airship\throwableToArray($e)]);
         $this->lens('login', ['post_response' => ['message' => \__('Incorrect username or passphrase. Please try again.'), 'status' => 'error']]);
     }
     if (!empty($userID)) {
         $userID = (int) $userID;
         $user = $this->acct->getUserAccount($userID);
         if ($user['enable_2factor']) {
             if (empty($post['two_factor'])) {
                 $post['two_factor'] = '';
             }
             $gauth = $this->twoFactorPreamble($userID);
             $checked = $gauth->validateCode($post['two_factor'], \time());
             if (!$checked) {
                 $fails = $airBrake->getFailedLoginAttempts($post['username'], $_SERVER['REMOTE_ADDR']) + 1;
                 // Instead of the password, seal a timestamped and
                 // signed message saying the password was correct.
                 // We use a signature with a key local to this Airship
                 // so attackers can't just spam a string constant to
                 // make the person decrypting these strings freak out
                 // and assume the password was compromised.
                 //
                 // False positives are bad. This gives the sysadmin a
                 // surefire way to reliably verify that a log entry is
                 // due to two-factor authentication failing.
                 $message = '**Note: The password was correct; ' . ' invalid 2FA token was provided.** ' . (new \DateTime('now'))->format(\AIRSHIP_DATE_FORMAT);
                 $signed = Base64UrlSafe::encode(Asymmetric::sign($message, $state->keyring['notary.online_signing_key'], true));
                 $airBrake->registerLoginFailure($post['username'], $_SERVER['REMOTE_ADDR'], $fails, new HiddenString($signed . $message));
                 $this->lens('login', ['post_response' => ['message' => \__('Incorrect username or passphrase. Please try again.'), 'status' => 'error']]);
             }
         }
         if ($user['session_canary']) {
             $_SESSION['session_canary'] = $user['session_canary'];
         } elseif ($this->config('password-reset.logout')) {
             $_SESSION['session_canary'] = $this->acct->createSessionCanary($userID);
         }
         // Regenerate session ID:
         Session::regenerate(true);
         $_SESSION['userid'] = (int) $userID;
         if (!empty($post['remember'])) {
             $autoPilot = Gears::getName('AutoPilot');
             if (IDE_HACKS) {
                 $autoPilot = new AutoPilot();
             }
             $httpsOnly = (bool) $autoPilot::isHTTPSConnection();
             Cookie::setcookie('airship_token', Symmetric::encrypt($this->airship_auth->createAuthToken($userID), $state->keyring['cookie.encrypt_key']), \time() + ($state->universal['long-term-auth-expire'] ?? self::DEFAULT_LONGTERMAUTH_EXPIRE), '/', $state->universal['session_config']['cookie_domain'] ?? '', $httpsOnly ?? false, true);
         }
         \Airship\redirect($this->airship_cabin_prefix);
     } else {
         $fails = $airBrake->getFailedLoginAttempts($post['username'], $_SERVER['REMOTE_ADDR']) + 1;
         // If the server is setup (with an EncryptionPublicKey) and the
         // number of failures is above the log threshold, this will
         // encrypt the password guess with the public key so that only
         // the person in possession of the secret key can decrypt it.
         $airBrake->registerLoginFailure($post['username'], $_SERVER['REMOTE_ADDR'], $fails, new HiddenString($post['passphrase']));
         $this->lens('login', ['post_response' => ['message' => \__('Incorrect username or passphrase. Please try again.'), 'status' => 'error']]);
     }
 }
Beispiel #10
0
 /**
  * Send the HTTP request, return the
  *
  * @param string $root
  * @return array
  */
 protected function getChannelUpdates(string $root) : array
 {
     $state = State::instance();
     if (IDE_HACKS) {
         $state->hail = new Hail(new Client());
     }
     foreach ($this->getChannelURLs() as $url) {
         $initiated = new \DateTime('now');
         $response = $state->hail->postSignedJSON($url . API::get('fetch_keys') . '/' . $root, $this->channelPublicKey);
         try {
             // We use a separate method for parsing this update:
             return $this->parseChannelUpdateResponse($response, $initiated);
         } catch (CouldNotUpdate $ex) {
             // Log the error message:
             $this->log($ex->getMessage(), LogLevel::ALERT, \Airship\throwableToArray($ex));
             // continue;
         }
     }
     // When all else fails, TransferException
     throw new TransferException(\__("All else has failed."));
 }
Beispiel #11
0
 /**
  * Return true if the Merkle roots match.
  *
  * Dear future security auditors: This is important.
  *
  * This employs challenge-response authentication:
  * @ref https://github.com/paragonie/airship/issues/13
  *
  * @param Channel $channel
  * @param MerkleTree $originalTree
  * @param TreeUpdate[] ...$updates
  * @return bool
  * @throws CouldNotUpdate
  */
 protected function verifyResponseWithPeers(Channel $channel, MerkleTree $originalTree, TreeUpdate ...$updates) : bool
 {
     $state = State::instance();
     $nodes = $this->updatesToNodes($updates);
     $tree = $originalTree->getExpandedTree(...$nodes);
     $maxUpdateIndex = \count($updates) - 1;
     $expectedRoot = $updates[$maxUpdateIndex]->getRoot();
     if (!\hash_equals($tree->getRoot(), $expectedRoot)) {
         // Calculated root did not match.
         self::$continuumLogger->store(LogLevel::EMERGENCY, 'Calculated Merkle root did not match the update.', [$tree->getRoot(), $expectedRoot]);
         throw new CouldNotUpdate(\__('Calculated Merkle root did not match the update.'));
     }
     if ($state->universal['auto-update']['ignore-peer-verification']) {
         // The user has expressed no interest in verification
         return true;
     }
     $peers = $channel->getPeerList();
     $numPeers = \count($peers);
     /**
      * These numbers are negotiable in future versions.
      *
      * If P is the set of trusted peer notaries (where ||P|| is the number
      * of trusted peer notaries):
      *
      * 1. At least 1 must return 'success'.
      * 2. At least ln(||P||) must return 'success'.
      * 3. At most e * ln(||P||) can timeout.
      * 4. If any peer disagrees with what we see, our
      *    result is discarded as invalid.
      *
      * The most harm a malicious peer can do is DoS if they
      * are selected.
      */
     $minSuccess = $channel->getAppropriatePeerSize();
     $maxFailure = (int) \min(\floor($minSuccess * M_E), $numPeers - 1);
     if ($maxFailure < 1) {
         $maxFailure = 1;
     }
     \Airship\secure_shuffle($peers);
     $success = $networkError = 0;
     /**
      * If any peers give a different answer, we're under attack.
      * If too many peers don't respond, assume they're being DDoS'd.
      * If enough peers respond in absolute agreement, we're good.
      */
     for ($i = 0; $i < $numPeers; ++$i) {
         try {
             if (!$this->checkWithPeer($peers[$i], $tree->getRoot())) {
                 // Merkle root mismatch? Abort.
                 return false;
             }
             ++$success;
         } catch (TransferException $ex) {
             self::$continuumLogger->store(LogLevel::EMERGENCY, 'A transfer exception occurred', \Airship\throwableToArray($ex));
             ++$networkError;
         }
         if ($success >= $minSuccess) {
             // We have enough good responses.
             return true;
         } elseif ($networkError >= $maxFailure) {
             // We can't give a confident response here.
             return false;
         }
     }
     self::$continuumLogger->store(LogLevel::EMERGENCY, 'We ran out of peers.', [$numPeers, $minSuccess, $maxFailure]);
     // Fail closed:
     return false;
 }
Beispiel #12
0
 /**
  * We're going to view a page's history
  *
  * @route pages/{string}/history/diff/{string}/{string}
  * @param string $cabin
  * @param string $leftUnique
  * @param string $rightUnique
  */
 public function pageHistoryDiff(string $cabin, string $leftUnique, string $rightUnique)
 {
     try {
         $left = $this->pg->getPageVersionByUniqueId($leftUnique);
         $right = $this->pg->getPageVersionByUniqueId($rightUnique);
         if ($left['page'] !== $right['page']) {
             throw new CustomPageNotFoundException(\__('Unique IDs for different pages.'));
         }
     } catch (CustomPageNotFoundException $ex) {
         $this->log('Page not found', LogLevel::NOTICE, ['exception' => \Airship\throwableToArray($ex)]);
         \Airship\redirect($this->airship_cabin_prefix . '/pages/' . \trim($cabin, '/'));
         return;
     }
     $this->setTemplateExtraData($cabin);
     $this->lens('pages/page_history_diff', ['left' => $left, 'right' => $right]);
 }
Beispiel #13
0
 /**
  * Grab post data, but only if the CSRF token is valid
  *
  * @param InputFilterContainer $filterContainer - Type filter for POST data
  * @param bool $ignoreCSRFToken - Don't validate CSRF tokens
  *
  * @return array|bool
  * @throws SecurityAlert
  */
 protected function post(InputFilterContainer $filterContainer = null, bool $ignoreCSRFToken = false)
 {
     if ($this->airship_http_method !== 'POST' || empty($_POST)) {
         return false;
     }
     if ($ignoreCSRFToken) {
         if ($filterContainer) {
             try {
                 return $filterContainer($_POST);
             } catch (\TypeError $ex) {
                 $this->log('Input validation threw a TypeError', LogLevel::ALERT, \Airship\throwableToArray($ex));
                 return false;
             }
         }
         return $_POST;
     }
     if ($this->airship_csrf->check()) {
         if ($filterContainer) {
             try {
                 return $filterContainer($_POST);
             } catch (\TypeError $ex) {
                 $this->log('Input validation threw a TypeError', LogLevel::ALERT, \Airship\throwableToArray($ex));
                 return false;
             }
         }
         return $_POST;
     }
     $state = State::instance();
     if ($state->universal['debug']) {
         // This is only thrown during development, to be noisy.
         throw new SecurityAlert(\__('CSRF validation failed'));
     }
     $this->log('CSRF validation failed', LogLevel::ALERT);
     return false;
 }
Beispiel #14
0
 /**
  * Process automatic updates:
  *
  * 1. Check if a new update is available.
  * 2. Download the upload file, store in a temporary file.
  * 3. Verify the signature (via Halite).
  * 4. Verify the update is recorded in Keyggdrasil.
  * 5. If all is well, run the update script.
  */
 public function autoUpdate()
 {
     $debugArgs = ['supplier' => $this->supplier->getName(), 'name' => $this->name];
     try {
         /**
          * @var UpdateInfo[]
          */
         $updateInfoArray = $this->updateCheck($this->supplier->getName(), $this->name, $this->manifest['version']);
         foreach ($updateInfoArray as $updateInfo) {
             if (!$this->checkVersionSettings($updateInfo, $this->manifest['version'])) {
                 $this->log('Skipping Motif update', LogLevel::INFO, ['info' => $updateInfo->getResponse(), 'new_version' => $updateInfo->getVersion(), 'current_version' => $this->manifest['version']]);
                 continue;
             }
             /**
              * @var UpdateFile
              */
             $updateFile = $this->downloadUpdateFile($updateInfo);
             $this->log('Downloaded Motif update file', LogLevel::DEBUG, $debugArgs);
             if ($this->bypassSecurityAndJustInstall) {
                 $this->log('Motif update verification bypassed', LogLevel::ALERT, $debugArgs);
                 $this->install($updateInfo, $updateFile);
                 return;
             }
             /**
              * Don't proceed unless we've verified the signatures
              * and the relevant entries in Keyggdrasil
              */
             if ($this->verifyUpdateSignature($updateInfo, $updateFile)) {
                 if ($this->checkKeyggdrasil($updateInfo, $updateFile)) {
                     $this->install($updateInfo, $updateFile);
                 } else {
                     $this->log('Keyggdrasil check failed for this Motif', LogLevel::ERROR, $debugArgs);
                     self::$continuumLogger->store(LogLevel::ALERT, 'Motif update failed -- checksum not registered in Keyggdrasil', $this->getLogContext($updateInfo, $updateFile));
                 }
             } else {
                 $this->log('Signature check failed for this Motif', LogLevel::ALERT, $debugArgs);
                 self::$continuumLogger->store(LogLevel::ALERT, 'Motif update failed -- invalid signature', $this->getLogContext($updateInfo, $updateFile));
             }
         }
     } catch (NoAPIResponse $ex) {
         // We should log this.
         $this->log('Automatic update failure: NO API Response.', LogLevel::CRITICAL, \Airship\throwableToArray($ex));
         self::$continuumLogger->store(LogLevel::ALERT, 'Motif update failed -- no API Response', ['action' => 'UPDATE', 'name' => $this->name, 'supplier' => $this->supplier->getName(), 'type' => $this->type]);
     }
 }
Beispiel #15
0
 /**
  * Trigger the package install process
  */
 public function updatePackage()
 {
     $expected = ['package', 'supplier', 'type', 'version'];
     if (!\Airship\all_keys_exist($expected, $_POST)) {
         \Airship\json_response(['status' => 'ERROR', 'message' => \__('Incomplete request.')]);
     }
     try {
         $filter = new SkyportFilter();
         $_POST = $filter($_POST);
     } catch (\TypeError $ex) {
         $this->log("Input violation", LogLevel::ALERT, \Airship\throwableToArray($ex));
         \Airship\json_response(['status' => 'ERROR', 'message' => \__('Invalid input.')]);
     }
     /**
      * @security We need to guarantee RCE isn't possible:
      */
     $args = \implode(' ', [\escapeshellarg(Util::charWhitelist($_POST['type'], Util::PRINTABLE_ASCII)), \escapeshellarg(Util::charWhitelist($_POST['supplier'], Util::PRINTABLE_ASCII) . '/' . Util::charWhitelist($_POST['package'], Util::PRINTABLE_ASCII)), \escapeshellarg(Util::charWhitelist($_POST['version'], Util::PRINTABLE_ASCII))]);
     $output = \shell_exec('php -dphar.readonly=0 ' . ROOT . '/CommandLine/update_one.sh ' . $args);
     \Airship\json_response(['status' => 'OK', 'message' => $output]);
 }
Beispiel #16
0
 /**
  * Are any updates available?
  *
  * @param string $supplier
  * @param string $packageName
  * @param string $minVersion
  * @param string $apiEndpoint
  *
  * @return UpdateInfo[]
  *
  * @throws \Airship\Alerts\Hail\NoAPIResponse
  */
 public function updateCheck(string $supplier = '', string $packageName = '', string $minVersion = '', string $apiEndpoint = 'version') : array
 {
     if (empty($supplier)) {
         $supplier = $this->supplier->getName();
     }
     $channelsConfigured = $this->supplier->getChannels();
     if (empty($channelsConfigured)) {
         throw new NoAPIResponse(\trk('errors.hail.no_channel_configured'));
     }
     foreach ($channelsConfigured as $channelName) {
         $channel = $this->getChannel($channelName);
         $publicKey = $channel->getPublicKey();
         foreach ($channel->getAllURLs() as $ch) {
             try {
                 $response = $this->hail->postSignedJSON($ch . API::get($apiEndpoint), $publicKey, ['type' => $this->type, 'supplier' => $supplier, 'package' => $packageName, 'minimum' => $minVersion]);
                 if ($response['status'] === 'error') {
                     $this->log($response['error'], LogLevel::ERROR, ['response' => $response, 'channel' => $ch, 'supplier' => $supplier, 'type' => $this->type, 'package' => $packageName]);
                     continue;
                 }
                 $updates = [];
                 foreach ($response['versions'] as $update) {
                     $updates[] = new UpdateInfo($update, $ch, $publicKey, $supplier, $packageName);
                 }
                 if (empty($updates)) {
                     $this->log('No updates found.', LogLevel::DEBUG, ['type' => \get_class($this), 'supplier' => $supplier, 'package' => $packageName, 'channelName' => $channelName, 'channel' => $ch]);
                     return [];
                 }
                 return $this->sortUpdatesByVersion(...$updates);
             } catch (SignatureFailed $ex) {
                 // Log? Definitely suppress, however.
                 $this->log('Automatic update - signature failure. (' . \get_class($ex) . ')', LogLevel::ALERT, ['exception' => \Airship\throwableToArray($ex), 'channelName' => $channelName, 'channel' => $ch]);
             } catch (TransferException $ex) {
                 // Log? Definitely suppress, however.
                 $this->log('Automatic update failure. (' . \get_class($ex) . ')', LogLevel::WARNING, ['exception' => \Airship\throwableToArray($ex), 'channelName' => $channelName, 'channel' => $ch]);
             }
         }
     }
     throw new NoAPIResponse(\trk('errors.hail.no_channel_responded'));
 }
Beispiel #17
0
 /**
  * Let's do an automatic login
  *
  * @param string $token
  * @param string $uid_idx
  * @param string $token_idx
  * @return bool
  * @throws LongTermAuthAlert (only in debug mode)
  * @throws \TypeError
  */
 protected function doAutoLogin(string $token, string $uid_idx, string $token_idx) : bool
 {
     if (!$this->airship_auth instanceof Authentication) {
         $this->tightenSecurityBolt();
     }
     $state = State::instance();
     try {
         $userId = $this->airship_auth->loginByToken($token);
         \Sodium\memzero($token);
         if (!$this->verifySessionCanary($userId, false)) {
             return false;
         }
         // Regenerate session ID:
         Session::regenerate(true);
         // Set session variable
         $_SESSION[$uid_idx] = $userId;
         $autoPilot = Gears::getName('AutoPilot');
         if (IDE_HACKS) {
             // We're using getName(), this is just to fool IDEs.
             $autoPilot = new AutoPilot();
         }
         $httpsOnly = (bool) $autoPilot::isHTTPSConnection();
         // Rotate the authentication token:
         Cookie::setcookie($token_idx, Symmetric::encrypt($this->airship_auth->rotateToken($token, $userId), $state->keyring['cookie.encrypt_key']), \time() + ($state->universal['long-term-auth-expire'] ?? self::DEFAULT_LONGTERMAUTH_EXPIRE), '/', '', $httpsOnly ?? false, true);
         return true;
     } catch (LongTermAuthAlert $e) {
         $state = State::instance();
         // Let's wipe our long-term authentication cookies
         Cookie::setcookie($token_idx, null, 0, '/', '', $httpsOnly ?? false, true);
         // Let's log this incident
         if (\property_exists($this, 'log')) {
             $this->log($e->getMessage(), LogLevel::CRITICAL, ['exception' => \Airship\throwableToArray($e)]);
         } else {
             $state->logger->log(LogLevel::CRITICAL, $e->getMessage(), ['exception' => \Airship\throwableToArray($e)]);
         }
         // In debug mode, re-throw the exception:
         if ($state->universal['debug']) {
             throw $e;
         }
     }
     return false;
 }