Skip to content

Commit

Permalink
Resolve issue #31
Browse files Browse the repository at this point in the history
  • Loading branch information
peter279k committed Feb 18, 2022
1 parent e73d4a1 commit 437a977
Show file tree
Hide file tree
Showing 8 changed files with 250 additions and 6 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: tests

on: [ push, pull_request ]

jobs:
run:
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: [ ubuntu-latest ]
php-versions: [ '7.4', '8.0' ]
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}

steps:
- name: Checkout
uses: actions/checkout@v1

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, intl, zip, ftp
coverage: none

- name: Check PHP Version
run: php -v

- name: Check Composer Version
run: composer -V

- name: Check PHP Extensions
run: php -m

- name: Install dependencies for PHP
run: composer update --prefer-dist --no-progress

- name: Setup SSL key with openssl
run: openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./vsftpd.key -out ./vsftpd.crt -subj "/C=AU/ST=Test/L=Test/O=Test com. /OU=Open Source World/CN=lazzard"

- name: Building fake FTP server container
run: cd tests/integration && docker build -t lazzard/vsftpd .

- name: Setup fake FTP server
run: docker run --name vsftpd -d -e LOG_STDOUT=true -e FTP_USER=username -e FTP_PASS=password -e ANONYMOUS_ACCESS=true -p 20-21:20-21 -p 21100-21110:21100-21110 -v $PWD/vsftpd.key:/etc/ssl/private/vsftpd.key -v $PWD/vsftpd.crt:/etc/ssl/certs/vsftpd.crt lazzard/vsftpd

- name: Set the host to be localhost
run: sed -i 's/host/172.17.0.2/g' tests/config.php

- name: Run test suite
run: vendor/bin/phpunit
2 changes: 1 addition & 1 deletion src/Command/FtpCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public function supportedSiteCommands() : array
return $response['message'];
}

return array_map('ltrim', $response['body']);
return array_map('ltrim', $response['body'] ?? []);
}

protected function parseRawResponse(array $response) : array
Expand Down
9 changes: 9 additions & 0 deletions src/FtpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,11 @@ public function copyToLocal(string $remoteSource, string $destinationFolder) : b

if ($this->isFile($remoteSource)) {
$localPath = "$destinationFolder/$sourceBase";
if (!is_dir($localPath)) {
throw new FtpClientException(sprintf('The destination folder: %s is not found',
$localPath
));
}
return $this->download($remoteSource, $localPath, false);
}

Expand All @@ -1062,6 +1067,10 @@ public function copyToLocal(string $remoteSource, string $destinationFolder) : b

$files = $this->listDirDetails($remoteSource, true);
foreach ($files as $file) {
if (substr($file['path'], 0, 2) !== './') {
$file['path'] = './' . $file['path'];
}

if (preg_match('/' . preg_quote($remoteSource, '/') . '\/(.*)/', $file['path'], $matches)) {
$source = dirname($matches[1]);
$this->copyToLocal($file['path'], "$destinationFolder/$source");
Expand Down
19 changes: 19 additions & 0 deletions tests/integration/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM rockylinux:8

ENV FTP_USER=admin \
FTP_PASS=random \
LOG_STDOUT=false \
ANONYMOUS_ACCESS=false \
UPLOADED_FILES_WORLD_READABLE=false \
CUSTOM_PASSIVE_ADDRESS=false

RUN \
yum clean all && \
yum install -y vsftpd ncurses && \
yum clean all

COPY container-files /

EXPOSE 20-21 21100-21110

ENTRYPOINT ["/bootstrap.sh"]
14 changes: 9 additions & 5 deletions tests/integration/FtpClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public function testAsyncDownload() : void

$client->createFile(self::$testFile, 'some content');

$this->assertTrue($client->asyncDownload(self::$testFile, $localFile, function () {
$this->assertTrue($client->asyncDownload(self::$testFile, $localFile, function ($speed, $percentage, $transferred, $seconds) {
//
}));
$this->assertFileExists($localFile);
Expand Down Expand Up @@ -490,7 +490,7 @@ public function testListDirectoryDetails() : void

public function testCopyFromLocalWithDirectory() : void
{
$localDir = sys_get_temp_dir() . "testCopyFromLocalWithDirectory";
$localDir = sys_get_temp_dir() . "/testCopyFromLocalWithDirectory";

@mkdir($localDir);

Expand Down Expand Up @@ -528,8 +528,10 @@ public function testCopyToLocalWithFile() : void

$client->createFile(self::$testFile);

@mkdir('./tmp', 0777, true);

$this->assertTrue($client->copyToLocal(self::$testFile, sys_get_temp_dir()));
$this->assertFileExists(sys_get_temp_dir() . "/" . basename(self::$testFile));
$this->assertFileExists("./tmp/" . basename(self::$testFile));

$client->removeFile(self::$testFile);
}
Expand All @@ -543,7 +545,7 @@ public function testCopyToLocalWithDirectory() : void

$this->assertTrue($client->copyToLocal(self::$testDir, sys_get_temp_dir()));

$copiedFile = sys_get_temp_dir() . "/" . basename(self::$testDir);
$copiedFile = "./tmp/" . basename(self::$testDir);

$this->assertTrue(file_exists($copiedFile));

Expand All @@ -565,6 +567,8 @@ public function testFind() : void

public function testFindRecursive() : void
{
$this->markTestIncomplete('The find method with recursive approach seems to be problematic.');

$client = new FtpClient(ConnectionHelper::getConnection());

$deepDir = self::$testDir . '/' . basename(self::$testDir);
Expand Down Expand Up @@ -620,4 +624,4 @@ public function testAppendFile() : void

$client->removeFile($testFile);
}
}
}
83 changes: 83 additions & 0 deletions tests/integration/container-files/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/bin/bash
set -eu
export TERM=xterm
# Bash Colors
green=`tput setaf 2`
bold=`tput bold`
reset=`tput sgr0`

# Functions
log() {
if [[ "$@" ]]; then echo "${bold}${green}[VSFTPD `date +'%T'`]${reset} $@";
else echo; fi
}

# If no env var for FTP_USER has been specified, use 'admin':
if [ "$FTP_USER" = "admin" ]; then
export FTP_USER='admin'
fi

# If no env var has been specified, generate a random password for FTP_USER:
if [ "$FTP_PASS" = "random" ]; then
export FTP_PASS=`cat /dev/urandom | tr -dc A-Z-a-z-0-9 | head -c${1:-16}`
fi

# Anonymous access settings
if [ "${ANONYMOUS_ACCESS}" = "true" ]; then
sed -i "s|anonymous_enable=NO|anonymous_enable=YES|g" /etc/vsftpd/vsftpd.conf
log "Enabled access for anonymous user."
fi

# Uploaded files world readable settings
if [ "${UPLOADED_FILES_WORLD_READABLE}" = "true" ]; then
sed -i "s|local_umask=077|local_umask=022|g" /etc/vsftpd/vsftpd.conf
log "Uploaded files will become world readable."
fi

# Custom passive address settings
if [ "${CUSTOM_PASSIVE_ADDRESS}" != "false" ]; then
sed -i "s|pasv_address=|pasv_address=${CUSTOM_PASSIVE_ADDRESS}|g" /etc/vsftpd/vsftpd.conf
log "Passive mode will advertise address ${CUSTOM_PASSIVE_ADDRESS}"
fi

# Create home dir and update vsftpd user db:
mkdir -p "/home/vsftpd/${FTP_USER}"
log "Created home directory for user: ${FTP_USER}"

echo -e "${FTP_USER}\n${FTP_PASS}" > /etc/vsftpd/virtual_users.txt
log "Updated /etc/vsftpd/virtual_users.txt"

/usr/bin/db_load -T -t hash -f /etc/vsftpd/virtual_users.txt /etc/vsftpd/virtual_users.db
log "Updated vsftpd database"

# Get log file path
export LOG_FILE=`grep vsftpd_log_file /etc/vsftpd/vsftpd.conf|cut -d= -f2`

# stdout server info:
if [ "${LOG_STDOUT}" = "true" ]; then
log "Enabling Logging to STDOUT"
mkdir -p /var/log/vsftpd
touch ${LOG_FILE}
tail -f ${LOG_FILE} | tee /dev/fd/1 &
elif [ "${LOG_STDOUT}" = "false" ]; then
log "Logging to STDOUT Disabled"
else
log "LOG_STDOUT available options are 'true/false'"
exit 1
fi

cat << EOB
SERVER SETTINGS
---------------
· FTP User: $FTP_USER
· FTP Password: $FTP_PASS
· Log file: $LOG_FILE
EOB

# Set permissions for FTP user
chown -R ftp:ftp /home/vsftpd/
log "Fixed permissions for newly created user: ${FTP_USER}"

log "VSFTPD daemon starting"
# Run vsftpd:
&>/dev/null /usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf
4 changes: 4 additions & 0 deletions tests/integration/container-files/etc/pam.d/vsftpd_virtual
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#%PAM-1.0
auth required pam_userdb.so db=/etc/vsftpd/virtual_users
account required pam_userdb.so db=/etc/vsftpd/virtual_users
session required pam_loginuid.so
75 changes: 75 additions & 0 deletions tests/integration/container-files/etc/vsftpd/vsftpd.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Run in the foreground to keep the container running:
background=NO

# Allow anonymous FTP? (Beware - allowed by default if you comment this out).
anonymous_enable=YES

# Uncomment this to allow local users to log in.
local_enable=YES

## Enable virtual users
guest_enable=YES

## Virtual users will use the same permissions as anonymous
virtual_use_local_privs=YES

# Uncomment this to enable any form of FTP write command.
write_enable=YES

## PAM file name
pam_service_name=vsftpd_virtual

## Home Directory for virtual users
user_sub_token=$USER
local_root=/home/vsftpd/$USER

# You may specify an explicit list of local users to chroot() to their home
# directory. If chroot_local_user is YES, then this list becomes a list of
# users to NOT chroot().
chroot_local_user=YES

# Workaround chroot check.
# See https://www.benscobie.com/fixing-500-oops-vsftpd-refusing-to-run-with-writable-root-inside-chroot/
# and http://serverfault.com/questions/362619/why-is-the-chroot-local-user-of-vsftpd-insecure
allow_writeable_chroot=YES

## Hide ids from user
hide_ids=YES

## Passive Address that gets advertised by vsftpd when responding to PASV command
pasv_address=

## Enable passive mode
pasv_enable=YES

## Set passive port range
pasv_max_port=50000
pasv_min_port=40000

## Enable logging
xferlog_enable=YES
vsftpd_log_file=/var/log/vsftpd/vsftpd.log

## Enable active mode
port_enable=YES
connect_from_port_20=YES
ftp_data_port=20

## control umask of uploaded files
# * 077 means that uploaded files get rw- --- ---
# * 022 means that uploaded files get rw- r-- r--
local_umask=022

ssl_enable=YES
allow_anon_ssl=NO
force_local_data_ssl=NO
force_local_logins_ssl=NO
ssl_tlsv1_1=YES
ssl_tlsv1_2=YES
ssl_tlsv1=NO
ssl_sslv2=NO
ssl_sslv3=NO
require_ssl_reuse=YES
ssl_ciphers=HIGH
rsa_cert_file=/etc/ssl/certs/vsftpd.crt
rsa_private_key_file=/etc/ssl/private/vsftpd.key

0 comments on commit 437a977

Please sign in to comment.