/**
  * Write file data into S3.
  * @task impl
  */
 public function writeFile($data, array $params)
 {
     $s3 = $this->newS3API();
     $name = 'phabricator/' . sha1(Filesystem::readRandomBytes(20));
     $s3->putObject($data, $this->getBucketName(), $name, $acl = 'private');
     return $name;
 }
 public function processRequest()
 {
     $user = $this->getRequest()->getUser();
     $old_token = id(new PhabricatorConduitCertificateToken())->loadOneWhere('userPHID = %s', $user->getPHID());
     if ($old_token) {
         $old_token->delete();
     }
     $token = id(new PhabricatorConduitCertificateToken())->setUserPHID($user->getPHID())->setToken(sha1(Filesystem::readRandomBytes(128)))->save();
     $panel = new AphrontPanelView();
     $panel->setHeader('Certificate Install Token');
     $panel->setWidth(AphrontPanelView::WIDTH_FORM);
     $panel->appendChild('<p class="aphront-form-instructions">Copy and paste this token into ' . 'the prompt given to you by "arc install-certificate":</p>' . '<p style="padding: 0 0 1em 4em;">' . '<strong>' . phutil_escape_html($token->getToken()) . '</strong>' . '</p>' . '<p class="aphront-form-instructions">arc will then complete the ' . 'install process for you.</p>');
     return $this->buildStandardPageResponse($panel, array('title' => 'Certificate Install Token'));
 }
 /**
  * @task internal
  */
 public static function getKey()
 {
     if (self::$key === null) {
         try {
             self::$key = Filesystem::readRandomBytes(128);
         } catch (Exception $ex) {
             // NOTE: We can't throw here! Otherwise we might get a stack trace
             // including the string that was passed to PhutilOpaqueEnvelope's
             // constructor. Just die() instead.
             die("Unable to read random bytes in PhutilOpaqueEnvelope. (This " . "causes an immediate process exit to avoid leaking the envelope " . "contents in a stack trace.)");
         }
     }
     return self::$key;
 }
 public function testReadRandomBytes()
 {
     $number_of_bytes = 1024;
     $data = Filesystem::readRandomBytes($number_of_bytes);
     $this->assertTrue(strlen($data) == $number_of_bytes);
     $data1 = Filesystem::readRandomBytes(128);
     $data2 = Filesystem::readRandomBytes(128);
     $this->assertFalse($data1 == $data2);
     $caught = null;
     try {
         Filesystem::readRandomBytes(0);
     } catch (Exception $ex) {
         $caught = $ex;
     }
     $this->assertTrue($caught instanceof Exception);
 }
 public function processRequest()
 {
     $user = $this->getRequest()->getUser();
     // Ideally we'd like to verify this, but it's fine to leave it unguarded
     // for now and verifying it would need some Ajax junk or for the user to
     // click a button or similar.
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     $old_token = id(new PhabricatorConduitCertificateToken())->loadOneWhere('userPHID = %s', $user->getPHID());
     if ($old_token) {
         $old_token->delete();
     }
     $token = id(new PhabricatorConduitCertificateToken())->setUserPHID($user->getPHID())->setToken(sha1(Filesystem::readRandomBytes(128)))->save();
     $panel = new AphrontPanelView();
     $panel->setHeader('Certificate Install Token');
     $panel->setWidth(AphrontPanelView::WIDTH_FORM);
     $panel->appendChild('<p class="aphront-form-instructions">Copy and paste this token into ' . 'the prompt given to you by "arc install-certificate":</p>' . '<p style="padding: 0 0 1em 4em;">' . '<strong>' . phutil_escape_html($token->getToken()) . '</strong>' . '</p>' . '<p class="aphront-form-instructions">arc will then complete the ' . 'install process for you.</p>');
     return $this->buildStandardPageResponse($panel, array('title' => 'Certificate Install Token'));
 }
 public static function generateNewPHID($type, array $config = array())
 {
     $owner = idx($config, 'owner');
     $parent = idx($config, 'parent');
     if (!$type) {
         throw new Exception("Can not generate PHID with no type.");
     }
     $entropy = Filesystem::readRandomBytes(20);
     $uniq = sha1($entropy);
     $uniq = substr($uniq, 0, 20);
     $phid = 'PHID-' . $type . '-' . $uniq;
     $phid_rec = new PhabricatorPHID();
     $phid_rec->setPHIDType($type);
     $phid_rec->setOwnerPHID($owner);
     $phid_rec->setParentPHID($parent);
     $phid_rec->setPHID($phid);
     $phid_rec->save();
     return $phid;
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $repository = id(new PhabricatorRepository())->load($this->id);
     if (!$repository) {
         return new Aphront404Response();
     }
     $views = array('basic' => 'Basics', 'tracking' => 'Tracking');
     $vcs = $repository->getVersionControlSystem();
     if ($vcs == DifferentialRevisionControlSystem::GIT) {
         if (!$repository->getDetail('github-token')) {
             $token = substr(base64_encode(Filesystem::readRandomBytes(8)), 0, 8);
             $repository->setDetail('github-token', $token);
             $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             $repository->save();
             unset($unguarded);
         }
         $views['github'] = 'GitHub';
     }
     $this->repository = $repository;
     if (!isset($views[$this->view])) {
         reset($views);
         $this->view = key($views);
     }
     $nav = new AphrontSideNavView();
     foreach ($views as $view => $name) {
         $nav->addNavItem(phutil_render_tag('a', array('class' => $view == $this->view ? 'aphront-side-nav-selected' : null, 'href' => '/repository/edit/' . $repository->getID() . '/' . $view . '/'), phutil_escape_html($name)));
     }
     $this->sideNav = $nav;
     switch ($this->view) {
         case 'basic':
             return $this->processBasicRequest();
         case 'tracking':
             return $this->processTrackingRequest();
         case 'github':
             return $this->processGithubRequest();
         default:
             throw new Exception("Unknown view.");
     }
 }
 public static function newChunkedFile(PhabricatorFileStorageEngine $engine, $length, array $params)
 {
     $file = self::initializeNewFile();
     $file->setByteSize($length);
     // TODO: We might be able to test the first chunk in order to figure
     // this out more reliably, since MIME detection usually examines headers.
     // However, enormous files are probably always either actually raw data
     // or reasonable to treat like raw data.
     $file->setMimeType('application/octet-stream');
     $chunked_hash = idx($params, 'chunkedHash');
     if ($chunked_hash) {
         $file->setContentHash($chunked_hash);
     } else {
         // See PhabricatorChunkedFileStorageEngine::getChunkedHash() for some
         // discussion of this.
         $seed = Filesystem::readRandomBytes(64);
         $hash = PhabricatorChunkedFileStorageEngine::getChunkedHashForInput($seed);
         $file->setContentHash($hash);
     }
     $file->setStorageEngine($engine->getEngineIdentifier());
     $file->setStorageHandle(PhabricatorFileChunk::newChunkHandle());
     $file->setStorageFormat(self::STORAGE_FORMAT_RAW);
     $file->setIsPartial(1);
     $file->readPropertiesFromParameters($params);
     return $file;
 }
 /**
  * We generate a unique chronological key for each story type because we want
  * to be able to page through the stream with a cursor (i.e., select stories
  * after ID = X) so we can efficiently perform filtering after selecting data,
  * and multiple stories with the same ID make this cumbersome without putting
  * a bunch of logic in the client. We could use the primary key, but that
  * would prevent publishing stories which happened in the past. Since it's
  * potentially useful to do that (e.g., if you're importing another data
  * source) build a unique key for each story which has chronological ordering.
  *
  * @return string A unique, time-ordered key which identifies the story.
  */
 private function generateChronologicalKey()
 {
     // Use the epoch timestamp for the upper 32 bits of the key. Default to
     // the current time if the story doesn't have an explicit timestamp.
     $time = nonempty($this->storyTime, time());
     // Generate a random number for the lower 32 bits of the key.
     $rand = head(unpack('L', Filesystem::readRandomBytes(4)));
     // On 32-bit machines, we have to get creative.
     if (PHP_INT_SIZE < 8) {
         // We're on a 32-bit machine.
         if (function_exists('bcadd')) {
             // Try to use the 'bc' extension.
             return bcadd(bcmul($time, bcpow(2, 32)), $rand);
         } else {
             // Do the math in MySQL. TODO: If we formalize a bc dependency, get
             // rid of this.
             $conn_r = id(new PhabricatorFeedStoryData())->establishConnection('r');
             $result = queryfx_one($conn_r, 'SELECT (%d << 32) + %d as N', $time, $rand);
             return $result['N'];
         }
     } else {
         // This is a 64 bit machine, so we can just do the math.
         return ($time << 32) + $rand;
     }
 }
 public function save()
 {
     if (!$this->getMailKey()) {
         $this->mailKey = sha1(Filesystem::readRandomBytes(20));
     }
     return parent::save();
 }
 private function writeEvents()
 {
     if (PhabricatorEnv::isReadOnly()) {
         return;
     }
     $events = $this->events;
     $random = Filesystem::readRandomBytes(32);
     $request_key = PhabricatorHash::digestForIndex($random);
     $host_id = $this->loadHostID(php_uname('n'));
     $context_id = $this->loadEventContextID($this->eventContext);
     $viewer_id = $this->loadEventViewerID($this->eventViewer);
     $label_map = $this->loadEventLabelIDs(mpull($events, 'getEventLabel'));
     foreach ($events as $event) {
         $event->setRequestKey($request_key)->setSampleRate($this->sampleRate)->setEventHostID($host_id)->setEventContextID($context_id)->setEventViewerID($viewer_id)->setEventLabelID($label_map[$event->getEventLabel()])->save();
     }
 }
 /**
  * We generate a unique chronological key for each story type because we want
  * to be able to page through the stream with a cursor (i.e., select stories
  * after ID = X) so we can efficiently perform filtering after selecting data,
  * and multiple stories with the same ID make this cumbersome without putting
  * a bunch of logic in the client. We could use the primary key, but that
  * would prevent publishing stories which happened in the past. Since it's
  * potentially useful to do that (e.g., if you're importing another data
  * source) build a unique key for each story which has chronological ordering.
  *
  * @return string A unique, time-ordered key which identifies the story.
  */
 private function generateChronologicalKey()
 {
     // Use the epoch timestamp for the upper 32 bits of the key. Default to
     // the current time if the story doesn't have an explicit timestamp.
     $time = nonempty($this->storyTime, time());
     // Generate a random number for the lower 32 bits of the key.
     $rand = head(unpack('L', Filesystem::readRandomBytes(4)));
     return ($time << 32) + $rand;
 }
Beispiel #13
0
 /**
  * Issue a new session key to this user. Phabricator supports different
  * types of sessions (like "web" and "conduit") and each session type may
  * have multiple concurrent sessions (this allows a user to be logged in on
  * multiple browsers at the same time, for instance).
  *
  * Note that this method is transport-agnostic and does not set cookies or
  * issue other types of tokens, it ONLY generates a new session key.
  *
  * You can configure the maximum number of concurrent sessions for various
  * session types in the Phabricator configuration.
  *
  * @param   string  Session type, like "web".
  * @return  string  Newly generated session key.
  */
 public function establishSession($session_type)
 {
     $conn_w = $this->establishConnection('w');
     if (strpos($session_type, '-') !== false) {
         throw new Exception("Session type must not contain hyphen ('-')!");
     }
     // We allow multiple sessions of the same type, so when a caller requests
     // a new session of type "web", we give them the first available session in
     // "web-1", "web-2", ..., "web-N", up to some configurable limit. If none
     // of these sessions is available, we overwrite the oldest session and
     // reissue a new one in its place.
     $session_limit = 1;
     switch ($session_type) {
         case 'web':
             $session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.web');
             break;
         case 'conduit':
             $session_limit = PhabricatorEnv::getEnvConfig('auth.sessions.conduit');
             break;
         default:
             throw new Exception("Unknown session type '{$session_type}'!");
     }
     $session_limit = (int) $session_limit;
     if ($session_limit <= 0) {
         throw new Exception("Session limit for '{$session_type}' must be at least 1!");
     }
     // Load all the currently active sessions.
     $sessions = queryfx_all($conn_w, 'SELECT type, sessionStart FROM %T WHERE userPHID = %s AND type LIKE %>', PhabricatorUser::SESSION_TABLE, $this->getPHID(), $session_type . '-');
     // Choose which 'type' we'll actually establish, i.e. what number we're
     // going to append to the basic session type. To do this, just check all
     // the numbers sequentially until we find an available session.
     $establish_type = null;
     $sessions = ipull($sessions, null, 'type');
     for ($ii = 1; $ii <= $session_limit; $ii++) {
         if (empty($sessions[$session_type . '-' . $ii])) {
             $establish_type = $session_type . '-' . $ii;
             break;
         }
     }
     // If we didn't find an available session, choose the oldest session and
     // overwrite it.
     if (!$establish_type) {
         $sessions = isort($sessions, 'sessionStart');
         $oldest = reset($sessions);
         $establish_type = $oldest['type'];
     }
     // Consume entropy to generate a new session key, forestalling the eventual
     // heat death of the universe.
     $entropy = Filesystem::readRandomBytes(20);
     $session_key = sha1($entropy);
     // UNGUARDED WRITES: Logging-in users don't have CSRF stuff yet.
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     queryfx($conn_w, 'INSERT INTO %T ' . '(userPHID, type, sessionKey, sessionStart)' . ' VALUES ' . '(%s, %s, %s, UNIX_TIMESTAMP()) ' . 'ON DUPLICATE KEY UPDATE ' . 'sessionKey = VALUES(sessionKey), ' . 'sessionStart = VALUES(sessionStart)', self::SESSION_TABLE, $this->getPHID(), $establish_type, $session_key);
     $log = PhabricatorUserLog::newLog($this, $this, PhabricatorUserLog::ACTION_LOGIN);
     $log->setDetails(array('session_type' => $session_type, 'session_issued' => $establish_type));
     $log->setSession($session_key);
     $log->save();
     return $session_key;
 }
 public static function newAES256IV()
 {
     // AES256 uses a 256 bit key, but the initialization vector length is
     // only 128 bits.
     $iv = Filesystem::readRandomBytes(phutil_units('128 bits in bytes'));
     return new PhutilOpaqueEnvelope($iv);
 }
 public function setPassword(PhutilOpaqueEnvelope $envelope)
 {
     if (!$this->getPHID()) {
         throw new Exception('You can not set a password for an unsaved user because their PHID ' . 'is a salt component in the password hash.');
     }
     if (!strlen($envelope->openEnvelope())) {
         $this->setPasswordHash('');
     } else {
         $this->setPasswordSalt(md5(Filesystem::readRandomBytes(32)));
         $hash = $this->hashPassword($envelope);
         $this->setPasswordHash($hash->openEnvelope());
     }
     return $this;
 }
represent its data. Pass a normal __soft_directory__ to compress, and a
__link__ to symlink to the compressed version once compression is complete.
The directory will be cloned into __scratch_dir__ and replaced with hardlinks.

This is primarily useful if you want to have checkouts of hundreds of different
commits in a version-controlled repository, so you can, e.g., run unit tests or
other readonly processes on them.

EOUSAGE
);
}
$soft = Filesystem::resolvePath($argv[1]);
$dst = $argv[2];
$root = Filesystem::resolvePath($argv[3]);
$hash_map = map_directory($soft);
$hardened_id = sha1(Filesystem::readRandomBytes(128));
$hardened_id = hash_path($hardened_id);
$hardened = $root . '/pit/' . $hardened_id;
execx('mkdir -p %s', $hardened);
$obj_root = $root . '/obj/';
foreach ($hash_map as $path => $info) {
    $hash = $info['hash'];
    $type = $info['type'];
    $obj = $obj_root . hash_path($hash);
    $link = $hardened . '/' . $path;
    $dir = dirname($link);
    if (!is_dir($dir)) {
        $ok = mkdir($dir, 0777, $recursive = true);
        if (!$ok) {
            throw new Exception(pht("Failed to make directory for '%s'!", $link));
        }
 public function save()
 {
     if (!$this->mailKey) {
         $this->mailKey = sha1(Filesystem::readRandomBytes(20));
     }
     $result = parent::save();
     if ($this->projectsNeedUpdate) {
         // If we've changed the project PHIDs for this task, update the link
         // table.
         ManiphestTaskProject::updateTaskProjects($this);
         $this->projectsNeedUpdate = false;
     }
     if ($this->subscribersNeedUpdate) {
         // If we've changed the subscriber PHIDs for this task, update the link
         // table.
         ManiphestTaskSubscriber::updateTaskSubscribers($this);
         $this->subscribersNeedUpdate = false;
     }
     return $result;
 }
 protected function executeChecks()
 {
     $safe_mode = ini_get('safe_mode');
     if ($safe_mode) {
         $message = pht("You have '%s' enabled in your PHP configuration, but Phabricator " . "will not run in safe mode. Safe mode has been deprecated in PHP 5.3 " . "and removed in PHP 5.4.\n\nDisable safe mode to continue.", 'safe_mode');
         $this->newIssue('php.safe_mode')->setIsFatal(true)->setName(pht('Disable PHP %s', 'safe_mode'))->setMessage($message)->addPHPConfig('safe_mode');
         return;
     }
     // Check for `disable_functions` or `disable_classes`. Although it's
     // possible to disable a bunch of functions (say, `array_change_key_case()`)
     // and classes and still have Phabricator work fine, it's unreasonably
     // difficult for us to be sure we'll even survive setup if these options
     // are enabled. Phabricator needs access to the most dangerous functions,
     // so there is no reasonable configuration value here which actually
     // provides a benefit while guaranteeing Phabricator will run properly.
     $disable_options = array('disable_functions', 'disable_classes');
     foreach ($disable_options as $disable_option) {
         $disable_value = ini_get($disable_option);
         if ($disable_value) {
             // By default Debian installs the pcntl extension but disables all of
             // its functions using configuration. Whitelist disabling these
             // functions so that Debian PHP works out of the box (we do not need to
             // call these functions from the web UI). This is pretty ridiculous but
             // it's not the users' fault and they haven't done anything crazy to
             // get here, so don't make them pay for Debian's unusual choices.
             // See: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=605571
             $fatal = true;
             if ($disable_option == 'disable_functions') {
                 $functions = preg_split('/[, ]+/', $disable_value);
                 $functions = array_filter($functions);
                 foreach ($functions as $k => $function) {
                     if (preg_match('/^pcntl_/', $function)) {
                         unset($functions[$k]);
                     }
                 }
                 if (!$functions) {
                     $fatal = false;
                 }
             }
             if ($fatal) {
                 $message = pht("You have '%s' enabled in your PHP configuration.\n\n" . "This option is not compatible with Phabricator. Remove " . "'%s' from your configuration to continue.", $disable_option, $disable_option);
                 $this->newIssue('php.' . $disable_option)->setIsFatal(true)->setName(pht('Remove PHP %s', $disable_option))->setMessage($message)->addPHPConfig($disable_option);
             }
         }
     }
     $overload_option = 'mbstring.func_overload';
     $func_overload = ini_get($overload_option);
     if ($func_overload) {
         $message = pht("You have '%s' enabled in your PHP configuration.\n\n" . "This option is not compatible with Phabricator. Disable " . "'%s' in your PHP configuration to continue.", $overload_option, $overload_option);
         $this->newIssue('php' . $overload_option)->setIsFatal(true)->setName(pht('Disable PHP %s', $overload_option))->setMessage($message)->addPHPConfig($overload_option);
     }
     $open_basedir = ini_get('open_basedir');
     if ($open_basedir) {
         // 'open_basedir' restricts which files we're allowed to access with
         // file operations. This might be okay -- we don't need to write to
         // arbitrary places in the filesystem -- but we need to access certain
         // resources. This setting is unlikely to be providing any real measure
         // of security so warn even if things look OK.
         $failures = array();
         try {
             $open_libphutil = class_exists('Future');
         } catch (Exception $ex) {
             $failures[] = $ex->getMessage();
         }
         try {
             $open_arcanist = class_exists('ArcanistDiffParser');
         } catch (Exception $ex) {
             $failures[] = $ex->getMessage();
         }
         $open_urandom = false;
         try {
             Filesystem::readRandomBytes(1);
             $open_urandom = true;
         } catch (FilesystemException $ex) {
             $failures[] = $ex->getMessage();
         }
         try {
             $tmp = new TempFile();
             file_put_contents($tmp, '.');
             $open_tmp = @fopen((string) $tmp, 'r');
             if (!$open_tmp) {
                 $failures[] = pht("Unable to read temporary file '%s'.", (string) $tmp);
             }
         } catch (Exception $ex) {
             $message = $ex->getMessage();
             $dir = sys_get_temp_dir();
             $failures[] = pht("Unable to open temp files from '%s': %s", $dir, $message);
         }
         $issue = $this->newIssue('php.open_basedir')->setName(pht('Disable PHP %s', 'open_basedir'))->addPHPConfig('open_basedir');
         if ($failures) {
             $message = pht("Your server is configured with '%s', which prevents Phabricator " . "from opening files it requires access to.\n\n" . "Disable this setting to continue.\n\nFailures:\n\n%s", 'open_basedir', implode("\n\n", $failures));
             $issue->setIsFatal(true)->setMessage($message);
             return;
         } else {
             $summary = pht("You have '%s' configured in your PHP settings, which " . "may cause some features to fail.", 'open_basedir');
             $message = pht("You have '%s' configured in your PHP settings. Although this " . "setting appears permissive enough that Phabricator will work " . "properly, you may still run into problems because of it.\n\n" . "Consider disabling '%s'.", 'open_basedir', 'open_basedir');
             $issue->setSummary($summary)->setMessage($message);
         }
     }
 }
 public static function newChunkedFile(PhabricatorFileStorageEngine $engine, $length, array $params)
 {
     $file = self::initializeNewFile();
     $file->setByteSize($length);
     // NOTE: Once we receive the first chunk, we'll detect its MIME type and
     // update the parent file. This matters for large media files like video.
     $file->setMimeType('application/octet-stream');
     $chunked_hash = idx($params, 'chunkedHash');
     if ($chunked_hash) {
         $file->setContentHash($chunked_hash);
     } else {
         // See PhabricatorChunkedFileStorageEngine::getChunkedHash() for some
         // discussion of this.
         $seed = Filesystem::readRandomBytes(64);
         $hash = PhabricatorChunkedFileStorageEngine::getChunkedHashForInput($seed);
         $file->setContentHash($hash);
     }
     $file->setStorageEngine($engine->getEngineIdentifier());
     $file->setStorageHandle(PhabricatorFileChunk::newChunkHandle());
     // Chunked files are always stored raw because they do not actually store
     // data. The chunks do, and can be individually formatted.
     $file->setStorageFormat(PhabricatorFileRawStorageFormat::FORMATKEY);
     $file->setIsPartial(1);
     $file->readPropertiesFromParameters($params);
     return $file;
 }
 public function processRequest(AphrontRequest $request)
 {
     $viewer = $request->getUser();
     $user = $this->getUser();
     $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession($viewer, $request, '/settings/');
     $vcspassword = id(new PhabricatorRepositoryVCSPassword())->loadOneWhere('userPHID = %s', $user->getPHID());
     if (!$vcspassword) {
         $vcspassword = id(new PhabricatorRepositoryVCSPassword());
         $vcspassword->setUserPHID($user->getPHID());
     }
     $panel_uri = $this->getPanelURI('?saved=true');
     $errors = array();
     $e_password = true;
     $e_confirm = true;
     if ($request->isFormPost()) {
         if ($request->getBool('remove')) {
             if ($vcspassword->getID()) {
                 $vcspassword->delete();
                 return id(new AphrontRedirectResponse())->setURI($panel_uri);
             }
         }
         $new_password = $request->getStr('password');
         $confirm = $request->getStr('confirm');
         if (!strlen($new_password)) {
             $e_password = pht('Required');
             $errors[] = pht('Password is required.');
         } else {
             $e_password = null;
         }
         if (!strlen($confirm)) {
             $e_confirm = pht('Required');
             $errors[] = pht('You must confirm the new password.');
         } else {
             $e_confirm = null;
         }
         if (!$errors) {
             $envelope = new PhutilOpaqueEnvelope($new_password);
             try {
                 // NOTE: This test is against $viewer (not $user), so that the error
                 // message below makes sense in the case that the two are different,
                 // and because an admin reusing their own password is bad, while
                 // system agents generally do not have passwords anyway.
                 $same_password = $viewer->comparePassword($envelope);
             } catch (PhabricatorPasswordHasherUnavailableException $ex) {
                 // If we're missing the hasher, just let the user continue.
                 $same_password = false;
             }
             if ($new_password !== $confirm) {
                 $e_password = pht('Does Not Match');
                 $e_confirm = pht('Does Not Match');
                 $errors[] = pht('Password and confirmation do not match.');
             } else {
                 if ($same_password) {
                     $e_password = pht('Not Unique');
                     $e_confirm = pht('Not Unique');
                     $errors[] = pht('This password is the same as another password associated ' . 'with your account. You must use a unique password for ' . 'VCS access.');
                 } else {
                     if (PhabricatorCommonPasswords::isCommonPassword($new_password)) {
                         $e_password = pht('Very Weak');
                         $e_confirm = pht('Very Weak');
                         $errors[] = pht('This password is extremely weak: it is one of the most common ' . 'passwords in use. Choose a stronger password.');
                     }
                 }
             }
             if (!$errors) {
                 $vcspassword->setPassword($envelope, $user);
                 $vcspassword->save();
                 return id(new AphrontRedirectResponse())->setURI($panel_uri);
             }
         }
     }
     $title = pht('Set VCS Password');
     $form = id(new AphrontFormView())->setUser($viewer)->appendRemarkupInstructions(pht('To access repositories hosted by Phabricator over HTTP, you must ' . 'set a version control password. This password should be unique.' . "\n\n" . "This password applies to all repositories available over " . "HTTP."));
     if ($vcspassword->getID()) {
         $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setLabel(pht('Current Password'))->setDisabled(true)->setValue('********************'));
     } else {
         $form->appendChild(id(new AphrontFormMarkupControl())->setLabel(pht('Current Password'))->setValue(phutil_tag('em', array(), pht('No Password Set'))));
     }
     $form->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setName('password')->setLabel(pht('New VCS Password'))->setError($e_password))->appendChild(id(new AphrontFormPasswordControl())->setDisableAutocomplete(true)->setName('confirm')->setLabel(pht('Confirm VCS Password'))->setError($e_confirm))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Change Password')));
     if (!$vcspassword->getID()) {
         $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
         $suggest = Filesystem::readRandomBytes(128);
         $suggest = preg_replace('([^A-Za-z0-9/!().,;{}^&*%~])', '', $suggest);
         $suggest = substr($suggest, 0, 20);
         if ($is_serious) {
             $form->appendRemarkupInstructions(pht('Having trouble coming up with a good password? Try this randomly ' . 'generated one, made by a computer:' . "\n\n" . "`%s`", $suggest));
         } else {
             $form->appendRemarkupInstructions(pht('Having trouble coming up with a good password? Try this ' . 'artisinal password, hand made in small batches by our expert ' . 'craftspeople: ' . "\n\n" . "`%s`", $suggest));
         }
     }
     $hash_envelope = new PhutilOpaqueEnvelope($vcspassword->getPasswordHash());
     $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Current Algorithm'))->setValue(PhabricatorPasswordHasher::getCurrentAlgorithmName($hash_envelope)));
     $form->appendChild(id(new AphrontFormStaticControl())->setLabel(pht('Best Available Algorithm'))->setValue(PhabricatorPasswordHasher::getBestAlgorithmName()));
     if (strlen($hash_envelope->openEnvelope())) {
         try {
             $can_upgrade = PhabricatorPasswordHasher::canUpgradeHash($hash_envelope);
         } catch (PhabricatorPasswordHasherUnavailableException $ex) {
             $can_upgrade = false;
             $errors[] = pht('Your VCS password is currently hashed using an algorithm which is ' . 'no longer available on this install.');
             $errors[] = pht('Because the algorithm implementation is missing, your password ' . 'can not be used.');
             $errors[] = pht('You can set a new password to replace the old password.');
         }
         if ($can_upgrade) {
             $errors[] = pht('The strength of your stored VCS password hash can be upgraded. ' . 'To upgrade, either: use the password to authenticate with a ' . 'repository; or change your password.');
         }
     }
     $object_box = id(new PHUIObjectBoxView())->setHeaderText($title)->setForm($form)->setFormErrors($errors);
     $remove_form = id(new AphrontFormView())->setUser($viewer);
     if ($vcspassword->getID()) {
         $remove_form->addHiddenInput('remove', true)->appendRemarkupInstructions(pht('You can remove your VCS password, which will prevent your ' . 'account from accessing repositories.'))->appendChild(id(new AphrontFormSubmitControl())->setValue(pht('Remove Password')));
     } else {
         $remove_form->appendRemarkupInstructions(pht('You do not currently have a VCS password set. If you set one, you ' . 'can remove it here later.'));
     }
     $remove_box = id(new PHUIObjectBoxView())->setHeaderText(pht('Remove VCS Password'))->setForm($remove_form);
     $saved = null;
     if ($request->getBool('saved')) {
         $saved = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_NOTICE)->setTitle(pht('Password Updated'))->appendChild(pht('Your VCS password has been updated.'));
     }
     return array($saved, $object_box, $remove_box);
 }
 public static function newChunkHandle()
 {
     $seed = Filesystem::readRandomBytes(64);
     return PhabricatorHash::digestForIndex($seed);
 }
Beispiel #22
0
 public static function runSetup()
 {
     header("Content-Type: text/plain");
     self::write("PHABRICATOR SETUP\n\n");
     // Force browser to stop buffering.
     self::write(str_repeat(' ', 2048));
     usleep(250000);
     self::write("This setup mode will guide you through setting up your " . "Phabricator configuration.\n");
     self::writeHeader("CORE CONFIGURATION");
     // NOTE: Test this first since other tests depend on the ability to
     // execute system commands and will fail if safe_mode is enabled.
     $safe_mode = ini_get('safe_mode');
     if ($safe_mode) {
         self::writeFailure();
         self::write("Setup failure! You have 'safe_mode' enabled. Phabricator will not " . "run in safe mode, and it has been deprecated in PHP 5.3 and removed " . "in PHP 5.4.\n");
         return;
     } else {
         self::write(" okay  PHP's deprecated 'safe_mode' is disabled.\n");
     }
     // NOTE: Also test this early since we can't include files from other
     // libraries if this is set strictly.
     $open_basedir = ini_get('open_basedir');
     if ($open_basedir) {
         // 'open_basedir' restricts which files we're allowed to access with
         // file operations. This might be okay -- we don't need to write to
         // arbitrary places in the filesystem -- but we need to access certain
         // resources. This setting is unlikely to be providing any real measure
         // of security so warn even if things look OK.
         try {
             $open_libphutil = class_exists('Future');
         } catch (Exception $ex) {
             $message = $ex->getMessage();
             self::write("Unable to load modules from libphutil: {$message}\n");
             $open_libphutil = false;
         }
         try {
             $open_arcanist = class_exists('ArcanistDiffParser');
         } catch (Exception $ex) {
             $message = $ex->getMessage();
             self::write("Unable to load modules from Arcanist: {$message}\n");
             $open_arcanist = false;
         }
         $open_urandom = false;
         try {
             Filesystem::readRandomBytes(1);
             $open_urandom = true;
         } catch (FilesystemException $ex) {
             self::write($ex->getMessage() . "\n");
         }
         try {
             $tmp = new TempFile();
             file_put_contents($tmp, '.');
             $open_tmp = @fopen((string) $tmp, 'r');
         } catch (Exception $ex) {
             $message = $ex->getMessage();
             $dir = sys_get_temp_dir();
             self::write("Unable to open temp files from '{$dir}': {$message}\n");
             $open_tmp = false;
         }
         if (!$open_urandom || !$open_tmp || !$open_libphutil || !$open_arcanist) {
             self::writeFailure();
             self::write("Setup failure! Your server is configured with 'open_basedir' in " . "php.ini which prevents Phabricator from opening files it needs to " . "access. Either make the setting more permissive or remove it. It " . "is unlikely you derive significant security benefits from having " . "this configured; files outside this directory can still be " . "accessed through system command execution.");
             return;
         } else {
             self::write("[WARN] You have an 'open_basedir' configured in your php.ini. " . "Although the setting seems permissive enough that Phabricator " . "will run properly, you may run into problems because of it. It is " . "unlikely you gain much real security benefit from having it " . "configured, because the application can still access files outside " . "the 'open_basedir' by running system commands.\n");
         }
     } else {
         self::write(" okay  'open_basedir' is not set.\n");
     }
     if (!PhabricatorEnv::getEnvConfig('security.alternate-file-domain')) {
         self::write("[WARN] You have not configured 'security.alternate-file-domain'. " . "This makes your installation vulnerable to attack. Make sure you " . "read the documentation for this parameter and understand the " . "consequences of leaving it unconfigured.\n");
     }
     $path = getenv('PATH');
     if (empty($path)) {
         self::writeFailure();
         self::write("Setup failure! The environmental \$PATH variable is empty. " . "Phabricator needs to execute system commands like 'svn', 'git', " . "'hg', and 'diff'. Set up your webserver so that it passes a valid " . "\$PATH to the PHP process.\n\n");
         if (php_sapi_name() == 'fpm-fcgi') {
             self::write("You're running php-fpm, so the easiest way to do this is to add " . "this line to your php-fpm.conf:\n\n" . "  env[PATH] = /usr/local/bin:/usr/bin:/bin\n\n" . "Then restart php-fpm.\n");
         }
         return;
     } else {
         self::write(" okay  \$PATH is nonempty.\n");
     }
     self::write("[OKAY] Core configuration OKAY.\n");
     self::writeHeader("REQUIRED PHP EXTENSIONS");
     $extensions = array('mysql', 'hash', 'json', 'openssl', 'mbstring', 'iconv', 'curl');
     foreach ($extensions as $extension) {
         $ok = self::requireExtension($extension);
         if (!$ok) {
             self::writeFailure();
             self::write("Setup failure! Install PHP extension '{$extension}'.");
             return;
         }
     }
     list($err, $stdout, $stderr) = exec_manual('which php');
     if ($err) {
         self::writeFailure();
         self::write("Unable to locate 'php' on the command line from the web " . "server. Verify that 'php' is in the webserver's PATH.\n" . "   err: {$err}\n" . "stdout: {$stdout}\n" . "stderr: {$stderr}\n");
         return;
     } else {
         self::write(" okay  PHP binary found on the command line.\n");
         $php_bin = trim($stdout);
     }
     // NOTE: In cPanel + suphp installs, 'php' may be the PHP CGI SAPI, not the
     // PHP CLI SAPI. proc_open() will pass the environment to the child process,
     // which will re-execute the webpage (causing an infinite number of
     // processes to spawn). To test that the 'php' binary is safe to execute,
     // we call php_sapi_name() using "env -i" to wipe the environment so it
     // doesn't execute another reuqest if it's the wrong binary. We can't use
     // "-r" because php-cgi doesn't support that flag.
     $tmp_file = new TempFile('sapi.php');
     Filesystem::writeFile($tmp_file, '<?php echo php_sapi_name();');
     list($err, $stdout, $stderr) = exec_manual('/usr/bin/env -i %s -f %s', $php_bin, $tmp_file);
     if ($err) {
         self::writeFailure();
         self::write("Unable to execute 'php' on the command line from the web " . "server.\n" . "   err: {$err}\n" . "stdout: {$stdout}\n" . "stderr: {$stderr}\n");
         return;
     } else {
         self::write(" okay  PHP is available from the command line.\n");
         $sapi = trim($stdout);
         if ($sapi != 'cli') {
             self::writeFailure();
             self::write("The 'php' binary on this system uses the '{$sapi}' SAPI, but the " . "'cli' SAPI is expected. Replace 'php' with the php-cli SAPI " . "binary, or edit your webserver configuration so the first 'php' " . "in PATH is the 'cli' SAPI.\n\n" . "If you're running cPanel with suphp, the easiest way to fix this " . "is to add '/usr/local/bin' before '/usr/bin' for 'env_path' in " . "suconf.php:\n\n" . '  env_path="/bin:/usr/local/bin:/usr/bin"' . "\n\n");
             return;
         } else {
             self::write(" okay  'php' is CLI SAPI.\n");
         }
     }
     $root = dirname(phutil_get_library_root('phabricator'));
     // On RHEL6, doing a distro install of pcntl makes it available from the
     // CLI binary but not from the Apache module. This isn't entirely
     // unreasonable and we don't need it from Apache, so do an explicit test
     // for CLI availability.
     list($err, $stdout, $stderr) = exec_manual('php %s', "{$root}/scripts/setup/pcntl_available.php");
     if ($err) {
         self::writeFailure();
         self::write("Unable to execute scripts/setup/pcntl_available.php to " . "test for the availability of pcntl from the CLI.\n" . "   err: {$err}\n" . "stdout: {$stdout}\n" . "stderr: {$stderr}\n");
         return;
     } else {
         if (trim($stdout) == 'YES') {
             self::write(" okay  pcntl is available from the command line.\n");
             self::write("[OKAY] All extensions OKAY\n");
         } else {
             self::write(" warn  pcntl is not available!\n");
             self::write("[WARN] *** WARNING *** pcntl extension not available. " . "You will not be able to run daemons.\n");
         }
     }
     self::writeHeader("GIT SUBMODULES");
     if (!Filesystem::pathExists($root . '/.git')) {
         self::write(" skip  Not a git clone.\n\n");
     } else {
         list($info) = execx('(cd %s && git submodule status)', $root);
         foreach (explode("\n", rtrim($info)) as $line) {
             $matches = null;
             if (!preg_match('/^(.)([0-9a-f]{40}) (\\S+)(?: |$)/', $line, $matches)) {
                 self::writeFailure();
                 self::write("Setup failure! 'git submodule' produced unexpected output:\n" . $line);
                 return;
             }
             $status = $matches[1];
             $module = $matches[3];
             switch ($status) {
                 case '-':
                 case '+':
                 case 'U':
                     self::writeFailure();
                     self::write("Setup failure! Git submodule '{$module}' is not up to date. " . "Run:\n\n" . "  cd {$root} && git submodule update --init\n\n" . "...to update submodules.");
                     return;
                 case ' ':
                     self::write(" okay  Git submodule '{$module}' up to date.\n");
                     break;
                 default:
                     self::writeFailure();
                     self::write("Setup failure! 'git submodule' reported unknown status " . "'{$status}' for submodule '{$module}'. This is a bug; report " . "it to the Phabricator maintainers.");
                     return;
             }
         }
     }
     self::write("[OKAY] All submodules OKAY.\n");
     self::writeHeader("BASIC CONFIGURATION");
     $env = PhabricatorEnv::getEnvConfig('phabricator.env');
     if ($env == 'production' || $env == 'default' || $env == 'development') {
         self::writeFailure();
         self::write("Setup failure! Your PHABRICATOR_ENV is set to '{$env}', which is " . "a Phabricator environmental default. You should create a custom " . "environmental configuration instead of editing the defaults " . "directly. See this document for instructions:\n");
         self::writeDoc('article/Configuration_Guide.html');
         return;
     } else {
         $host = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
         $host_uri = new PhutilURI($host);
         $protocol = $host_uri->getProtocol();
         $allowed_protocols = array('http' => true, 'https' => true);
         if (empty($allowed_protocols[$protocol])) {
             self::writeFailure();
             self::write("You must specify the protocol over which your host works (e.g.: " . "\"http:// or https://\")\nin your custom config file.\nRefer to " . "'default.conf.php' for documentation on configuration options.\n");
             return;
         }
         if (preg_match('/.*\\/$/', $host)) {
             self::write(" okay  phabricator.base-uri protocol\n");
         } else {
             self::writeFailure();
             self::write("You must add a trailing slash at the end of the host\n(e.g.: " . "\"http://phabricator.example.com/ instead of " . "http://phabricator.example.com\")\nin your custom config file." . "\nRefer to 'default.conf.php' for documentation on configuration " . "options.\n");
             return;
         }
         $host_domain = $host_uri->getDomain();
         if (strpos($host_domain, '.') !== false) {
             self::write(" okay  phabricator.base-uri domain\n");
         } else {
             self::writeFailure();
             self::write("You must host Phabricator on a domain that contains a dot ('.'). " . "The current domain, '{$host_domain}', does not have a dot, so some " . "browsers will not set cookies on it. For instance, " . "'http://example.com/ is OK, but 'http://example/' won't work. " . "If you are using localhost, create an entry in the hosts file like " . "'127.0.0.1 example.com', and access the localhost with " . "'http://example.com/'.");
             return;
         }
     }
     $timezone = nonempty(PhabricatorEnv::getEnvConfig('phabricator.timezone'), ini_get('date.timezone'));
     if (!$timezone) {
         self::writeFailure();
         self::write("Setup failure! Your configuration fails to specify a server " . "timezone. Either set 'date.timezone' in your php.ini or " . "'phabricator.timezone' in your Phabricator configuration. See the " . "PHP documentation for a list of supported timezones:\n\n" . "http://www.php.net/manual/en/timezones.php\n");
         return;
     } else {
         self::write(" okay  Timezone '{$timezone}' configured.\n");
     }
     self::write("[OKAY] Basic configuration OKAY\n");
     $issue_gd_warning = false;
     self::writeHeader('GD LIBRARY');
     if (extension_loaded('gd')) {
         self::write(" okay  Extension 'gd' is loaded.\n");
         $image_type_map = array('imagepng' => 'PNG', 'imagegif' => 'GIF', 'imagejpeg' => 'JPEG');
         foreach ($image_type_map as $function => $image_type) {
             if (function_exists($function)) {
                 self::write(" okay  Support for '{$image_type}' is available.\n");
             } else {
                 self::write(" warn  Support for '{$image_type}' is not available!\n");
                 $issue_gd_warning = true;
             }
         }
     } else {
         self::write(" warn  Extension 'gd' is not loaded.\n");
         $issue_gd_warning = true;
     }
     if ($issue_gd_warning) {
         self::write("[WARN] The 'gd' library is missing or lacks full support. " . "Phabricator will not be able to generate image thumbnails without " . "gd.\n");
     } else {
         self::write("[OKAY] 'gd' loaded and has full image type support.\n");
     }
     self::writeHeader('FACEBOOK INTEGRATION');
     $fb_auth = PhabricatorEnv::getEnvConfig('facebook.auth-enabled');
     if (!$fb_auth) {
         self::write(" skip  'facebook.auth-enabled' not enabled.\n");
     } else {
         self::write(" okay  'facebook.auth-enabled' is enabled.\n");
         $app_id = PhabricatorEnv::getEnvConfig('facebook.application-id');
         $app_secret = PhabricatorEnv::getEnvConfig('facebook.application-secret');
         if (!$app_id) {
             self::writeFailure();
             self::write("Setup failure! 'facebook.auth-enabled' is true but there is no " . "setting for 'facebook.application-id'.\n");
             return;
         } else {
             self::write(" okay  'facebook.application-id' is set.\n");
         }
         if (!is_string($app_id)) {
             self::writeFailure();
             self::write("Setup failure! 'facebook.application-id' should be a string.");
             return;
         } else {
             self::write(" okay  'facebook.application-id' is string.\n");
         }
         if (!$app_secret) {
             self::writeFailure();
             self::write("Setup failure! 'facebook.auth-enabled' is true but there is no " . "setting for 'facebook.application-secret'.");
             return;
         } else {
             self::write(" okay  'facebook.application-secret is set.\n");
         }
         self::write("[OKAY] Facebook integration OKAY\n");
     }
     self::writeHeader("MySQL DATABASE & STORAGE CONFIGURATION");
     $conf = PhabricatorEnv::newObjectFromConfig('mysql.configuration-provider');
     $conn_user = $conf->getUser();
     $conn_pass = $conf->getPassword();
     $conn_host = $conf->getHost();
     $timeout = ini_get('mysql.connect_timeout');
     if ($timeout > 5) {
         self::writeNote("Your MySQL connect timeout is very high ({$timeout} seconds). " . "Consider reducing it to 5 or below by setting " . "'mysql.connect_timeout' in your php.ini.");
     }
     self::write(" okay  Trying to connect to MySQL database " . "{$conn_user}@{$conn_host}...\n");
     ini_set('mysql.connect_timeout', 2);
     $conn_raw = PhabricatorEnv::newObjectFromConfig('mysql.implementation', array(array('user' => $conn_user, 'pass' => $conn_pass, 'host' => $conn_host, 'database' => null)));
     try {
         queryfx($conn_raw, 'SELECT 1');
         self::write(" okay  Connection successful!\n");
     } catch (AphrontQueryConnectionException $ex) {
         $message = $ex->getMessage();
         self::writeFailure();
         self::write("Setup failure! MySQL exception: {$message} \n" . "Edit Phabricator configuration keys 'mysql.user', " . "'mysql.host' and 'mysql.pass' to enable Phabricator " . "to connect.");
         return;
     }
     $engines = queryfx_all($conn_raw, 'SHOW ENGINES');
     $engines = ipull($engines, 'Support', 'Engine');
     $innodb = idx($engines, 'InnoDB');
     if ($innodb != 'YES' && $innodb != 'DEFAULT') {
         self::writeFailure();
         self::write("Setup failure! The 'InnoDB' engine is not available. Enable " . "InnoDB in your MySQL configuration. If you already created tables, " . "MySQL incorrectly used some other engine. You need to convert " . "them or drop and reinitialize them.");
         return;
     } else {
         self::write(" okay  InnoDB is available.\n");
     }
     $namespace = PhabricatorEnv::getEnvConfig('storage.default-namespace');
     $databases = queryfx_all($conn_raw, 'SHOW DATABASES');
     $databases = ipull($databases, 'Database', 'Database');
     if (empty($databases[$namespace . '_meta_data'])) {
         self::writeFailure();
         self::write("Setup failure! You haven't run 'bin/storage upgrade'. See this " . "article for instructions:\n");
         self::writeDoc('article/Configuration_Guide.html');
         return;
     } else {
         self::write(" okay  Databases have been initialized.\n");
     }
     $index_min_length = queryfx_one($conn_raw, 'SHOW VARIABLES LIKE %s', 'ft_min_word_len');
     $index_min_length = idx($index_min_length, 'Value', 4);
     if ($index_min_length >= 4) {
         self::writeNote("MySQL is configured with a 'ft_min_word_len' of 4 or greater, which " . "means you will not be able to search for 3-letter terms. Consider " . "setting this in your configuration:\n" . "\n" . "    [mysqld]\n" . "        ft_min_word_len=3\n" . "\n" . "Then optionally run:\n" . "\n" . "    REPAIR TABLE {$namespace}_search.search_documentfield QUICK;\n" . "\n" . "...to reindex existing documents.");
     }
     $max_allowed_packet = queryfx_one($conn_raw, 'SHOW VARIABLES LIKE %s', 'max_allowed_packet');
     $max_allowed_packet = idx($max_allowed_packet, 'Value', PHP_INT_MAX);
     $recommended_minimum = 1024 * 1024;
     if ($max_allowed_packet < $recommended_minimum) {
         self::writeNote("MySQL is configured with a small 'max_allowed_packet' " . "('{$max_allowed_packet}'), which may cause some large writes to " . "fail. Consider raising this to at least {$recommended_minimum}.");
     } else {
         self::write(" okay  max_allowed_packet = {$max_allowed_packet}.\n");
     }
     $local_key = 'storage.local-disk.path';
     $local_path = PhabricatorEnv::getEnvConfig($local_key);
     if ($local_path) {
         if (!Filesystem::pathExists($local_path) || !is_readable($local_path) || !is_writable($local_path)) {
             self::writeFailure();
             self::write("Setup failure! You have configured local disk storage but the " . "path you specified ('{$local_path}') does not exist or is not " . "readable or writable.\n");
             if ($open_basedir) {
                 self::write("You have an 'open_basedir' setting -- make sure Phabricator is " . "allowed to open files in the local storage directory.\n");
             }
             return;
         } else {
             self::write(" okay  Local disk storage exists and is writable.\n");
         }
     } else {
         self::write(" skip  Not configured for local disk storage.\n");
     }
     $selector = PhabricatorEnv::getEnvConfig('storage.engine-selector');
     try {
         $storage_selector_exists = class_exists($selector);
     } catch (Exception $ex) {
         $storage_selector_exists = false;
     }
     if ($storage_selector_exists) {
         self::write(" okay  Using '{$selector}' as a storage engine selector.\n");
     } else {
         self::writeFailure();
         self::write("Setup failure! You have configured '{$selector}' as a storage engine " . "selector but it does not exist or could not be loaded.\n");
         return;
     }
     self::write("[OKAY] Database and storage configuration OKAY\n");
     self::writeHeader("OUTBOUND EMAIL CONFIGURATION");
     $have_adapter = false;
     $is_ses = false;
     $adapter = PhabricatorEnv::getEnvConfig('metamta.mail-adapter');
     switch ($adapter) {
         case 'PhabricatorMailImplementationPHPMailerLiteAdapter':
             $have_adapter = true;
             if (!Filesystem::pathExists('/usr/bin/sendmail') && !Filesystem::pathExists('/usr/sbin/sendmail')) {
                 self::writeFailure();
                 self::write("Setup failure! You don't have a 'sendmail' binary on this system " . "but outbound email is configured to use sendmail. Install an MTA " . "(like sendmail, qmail or postfix) or use a different outbound " . "mail configuration. See this guide for configuring outbound " . "email:\n");
                 self::writeDoc('article/Configuring_Outbound_Email.html');
                 return;
             } else {
                 self::write(" okay  Sendmail is configured.\n");
             }
             break;
         case 'PhabricatorMailImplementationAmazonSESAdapter':
             $is_ses = true;
             $have_adapter = true;
             if (PhabricatorEnv::getEnvConfig('metamta.can-send-as-user')) {
                 self::writeFailure();
                 self::write("Setup failure! 'metamta.can-send-as-user' must be false when " . "configured with Amazon SES.");
                 return;
             } else {
                 self::write(" okay  Sender config looks okay.\n");
             }
             if (!PhabricatorEnv::getEnvConfig('amazon-ses.access-key')) {
                 self::writeFailure();
                 self::write("Setup failure! 'amazon-ses.access-key' is not set, but " . "outbound mail is configured to deliver via Amazon SES.");
                 return;
             } else {
                 self::write(" okay Amazon SES access key is set.\n");
             }
             if (!PhabricatorEnv::getEnvConfig('amazon-ses.secret-key')) {
                 self::writeFailure();
                 self::write("Setup failure! 'amazon-ses.secret-key' is not set, but " . "outbound mail is configured to deliver via Amazon SES.");
                 return;
             } else {
                 self::write(" okay Amazon SES secret key is set.\n");
             }
             if (PhabricatorEnv::getEnvConfig('metamta.send-immediately')) {
                 self::writeNote("Your configuration uses Amazon SES to deliver email but tries " . "to send it immediately. This will work, but it's slow. " . "Consider configuring the MetaMTA daemon.");
             }
             break;
         case 'PhabricatorMailImplementationTestAdapter':
             self::write(" skip  You have disabled outbound email.\n");
             break;
         default:
             self::write(" skip  Configured with a custom adapter.\n");
             break;
     }
     if ($have_adapter) {
         $default = PhabricatorEnv::getEnvConfig('metamta.default-address');
         if (!$default || $default == '*****@*****.**') {
             self::writeFailure();
             self::write("Setup failure! You have not set 'metamta.default-address'.");
             return;
         } else {
             self::write(" okay  metamta.default-address is set.\n");
         }
         if ($is_ses) {
             self::writeNote("Make sure you've verified your 'from' address ('{$default}') with " . "Amazon SES. Until you verify it, you will be unable to send mail " . "using Amazon SES.");
         }
         $domain = PhabricatorEnv::getEnvConfig('metamta.domain');
         if (!$domain || $domain == 'example.com') {
             self::writeFailure();
             self::write("Setup failure! You have not set 'metamta.domain'.");
             return;
         } else {
             self::write(" okay  metamta.domain is set.\n");
         }
         self::write("[OKAY] Mail configuration OKAY\n");
     }
     self::writeHeader('CONFIG CLASSES');
     foreach (PhabricatorEnv::getRequiredClasses() as $key => $instanceof) {
         $config = PhabricatorEnv::getEnvConfig($key);
         if (!$config) {
             self::writeNote("'{$key}' is not set.");
         } else {
             try {
                 $r = new ReflectionClass($config);
                 if (!$r->isSubclassOf($instanceof)) {
                     throw new Exception("Config setting '{$key}' must be an instance of '{$instanceof}'.");
                 } else {
                     if (!$r->isInstantiable()) {
                         throw new Exception("Config setting '{$key}' must be instantiable.");
                     }
                 }
             } catch (Exception $ex) {
                 self::writeFailure();
                 self::write("Setup failure! " . $ex->getMessage());
                 return;
             }
         }
     }
     self::write("[OKAY] Config classes OKAY\n");
     self::writeHeader('SUCCESS!');
     self::write("Congratulations! Your setup seems mostly correct, or at least fairly " . "reasonable.\n\n" . "*** NEXT STEP ***\n" . "Edit your configuration file (conf/{$env}.conf.php) and remove the " . "'phabricator.setup' line to finish installation.");
 }