Skip to content

Instantly share code, notes, and snippets.

@phuze
Last active February 21, 2022 03:22
Show Gist options
  • Save phuze/a0ec4cb4a79c15ec260bb532bb6c5536 to your computer and use it in GitHub Desktop.
Save phuze/a0ec4cb4a79c15ec260bb532bb6c5536 to your computer and use it in GitHub Desktop.
PHP model for interfacing with remote user management script. The remote script manages users in a Berkeley DB within a Linux environment. Read the full article: Building a Secure FTP Server — https://phuze.dev/building-a-secure-ftp-server
<?php
namespace MyApi\Models;
use phpseclib3\Net\SSH2;
use phpseclib3\Crypt\PublicKeyLoader;
/**
* FTP model
*/
class FtpModel
{
public function __construct()
{
# load private key
$this->key = PublicKeyLoader::load(file_get_contents('/path/to/private/key'));
# arrays to hold errors and/or success
$this->errors = [];
$this->success = [];
}
/**
* Connect to remote FTP server.
* Port is 22 and Timeout is 30 seconds.
*/
private function connectSSH()
{
$this->ssh = new SSH2('my.domain.com', 22, 30);
if (!$this->ssh->login('api', $this->key)) {
throw new Exception('Unable to establish SSH connection.');
}
}
/**
* Verify we have a valid username.
* Valid usernames may only include (a-z) and dashes (-)
*
* @param string $username
* @return bool
*/
private function isValidUsername(string $username)
{
if(preg_match('/[^a-z-]/i', $username)) {
return false;
}
return true;
}
/**
* Generate a cryptographically strong password.
* Resulting password length will be twice the length of
* the supplied length, and only include [0-9a-z]
*
* @return string
*/
private function genPassword(int $length = 8)
{
return bin2hex(openssl_random_pseudo_bytes($length));
}
private function parseResult(string $stdout)
{
# explode our stdout into individual lines
$lines = explode("\n", $stdout);
# iterate over the output lines
foreach($lines as $line) {
# capture any errors
if (strpos($line, 'ERROR:') !== false) {
$this->errors[] = $line;
}
if (strpos($line, 'SUCCESS:') !== false) {
$this->success[] = $line;
}
}
# if our success array is not empty
# (meaning a success line was found)
# then our command was successful
if(!empty($this->success)) {
return true;
}
return false;
}
public function addUser(string $username)
{
# dont allow invalid usernames
if(!$this->isValidUsername($username)) {
throw new Exception('Invalid username. Only lowercase a-z characters allowed.');
}
# generate a new password for the user
# note: only admins with local access to the FTP server
# may define a password manually
$password = $this->genPassword();
# connect to FTP server using SSH
$this->connectSSH();
# execute our remote user management script
$create = $this->ssh->exec("sudo /etc/vsftpd/users add {$username} {$password}");
# $create will be stdout, so we'll need to parse it to
# confirm if the action was successful
$result = $this->parseResult($create);
# return the result
return [
'success' => $result,
'message' => $result ? $this->success[0] : $this->errors[0],
'password' => $result ? $password : null
];
}
public function delUser(string $username)
{
# dont allow invalid usernames
if(!$this->isValidUsername($username)) {
throw new Exception('Invalid username. Only lowercase a-z characters allowed.');
}
# connect to FTP server using SSH
$this->connectSSH();
# execute our remote user management script
$delete = $this->ssh->exec("sudo /etc/vsftpd/users del {$username}");
# $delete will be stdout, so we'll need to parse it to
# confirm if the action was successful
$result = $this->parseResult($delete);
# return the result
return [
'success' => $result,
'message' => $result ? $this->success[0] : $this->errors[0]
];
}
public function editUser(string $username)
{
# dont allow invalid usernames
if(!$this->isValidUsername($username)) {
throw new Exception('Invalid username. Only lowercase a-z characters allowed.');
}
# generate a new password for the user
# note: only admins with local access to the FTP server
# may define a password manually
$password = $this->genPassword();
# connect to FTP server using SSH
$this->connectSSH();
# execute our remote user management script
$edit = $this->ssh->exec("sudo /etc/vsftpd/users edit {$username} {$password}");
# $edit will be stdout, so we'll need to parse it to
# confirm if the action was successful
$result = $this->parseResult($edit);
# return the result
return [
'success' => $result,
'message' => $result ? $this->success[0] : $this->errors[0],
'password' => $result ? $password : null
];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment