PHP Classes

File: scripts/ftp_daemon.php

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   scripts/ftp_daemon.php   Download  
File: scripts/ftp_daemon.php
Role: Example script
Content type: text/plain
Description: Example script
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 6 days ago
Size: 4,863 bytes
 

Contents

Class file image Download
#!/usr/bin/env php
<?php

chdir
(__DIR__ . "/../");

require_once
__DIR__ . '/../vendor/autoload.php';
require_once
__DIR__ . '/../src/functions.php';

use
BinktermPHP\Binkp\Logger;
use
BinktermPHP\Config;
use
BinktermPHP\Ftp\FtpServer;

function
parseArgs(array $argv): array
{
   
$args = [];
    foreach (
$argv as $arg) {
        if (
strpos($arg, '--') !== 0) {
            continue;
        }
        if (
strpos($arg, '=') !== false) {
            [
$key, $value] = explode('=', substr($arg, 2), 2);
           
$args[$key] = $value;
        } else {
           
$args[substr($arg, 2)] = true;
        }
    }
    return
$args;
}

function
showUsage(): void
{
    echo
"Usage: php scripts/ftp_daemon.php [options]\n";
    echo
"Options:\n";
    echo
" --host=HOST FTP bind host (default: FTPD_BIND_HOST or 0.0.0.0)\n";
    echo
" --port=PORT FTP bind port (default: FTPD_PORT or 2121)\n";
    echo
" --public-host=HOST Public host/IP advertised in PASV replies (default: FTPD_PUBLIC_HOST)\n";
    echo
" --pasv-start=PORT First passive data port (default: FTPD_PASSIVE_PORT_START or 2122)\n";
    echo
" --pasv-end=PORT Last passive data port (default: FTPD_PASSIVE_PORT_END or 2149)\n";
    echo
" --daemon Run as daemon (requires pcntl_fork)\n";
    echo
" --pid-file=FILE Write PID file (default: data/run/ftpd.pid)\n";
    echo
" --log-file=FILE Log file path (default: data/logs/ftpd.log)\n";
    echo
" --log-level=LEVEL Log level (default: INFO)\n";
    echo
" --no-console Disable console logging\n";
    echo
" --help Show this help message\n";
}

function
daemonize(): void
{
   
$pid = pcntl_fork();
    if (
$pid === -1) {
        die(
"Could not fork process\n");
    } elseif (
$pid) {
        echo
"FTP daemon started with PID: $pid\n";
        exit(
0);
    }

    if (
posix_setsid() === -1) {
        die(
"Could not detach from terminal\n");
    }

   
chdir('/');
   
umask(0);
   
fclose(STDIN);
   
fclose(STDOUT);
   
fclose(STDERR);
   
fopen('/dev/null', 'r');
   
fopen('/dev/null', 'a');
   
fopen('/dev/null', 'a');
}

function
setConsoleTitle(string $title): void
{
    echo
"\033]0;{$title}\007";
}

$args = parseArgs($argv);
if (isset(
$args['help'])) {
   
showUsage();
    exit(
0);
}

$enabled = strtolower((string)Config::env('FTPD_ENABLED', 'false')) === 'true';
if (!
$enabled) {
   
fwrite(STDERR, "FTPD is disabled. Set FTPD_ENABLED=true to run scripts/ftp_daemon.php.\n");
    exit(
1);
}

$host = (string)($args['host'] ?? Config::env('FTPD_BIND_HOST', '0.0.0.0'));
$port = (int)($args['port'] ?? Config::env('FTPD_PORT', '2121'));
$publicHost = (string)($args['public-host'] ?? Config::env('FTPD_PUBLIC_HOST', ''));
$passiveStart = (int)($args['pasv-start'] ?? Config::env('FTPD_PASSIVE_PORT_START', '2122'));
$passiveEnd = (int)($args['pasv-end'] ?? Config::env('FTPD_PASSIVE_PORT_END', '2149'));
$pidFile = (string)($args['pid-file'] ?? (__DIR__ . '/../data/run/ftpd.pid'));
$logFile = (string)($args['log-file'] ?? Config::getLogPath('ftpd.log'));
$logLevel = (string)($args['log-level'] ?? 'INFO');
$logToConsole = !isset($args['no-console']);

$logger = new Logger($logFile, $logLevel, $logToConsole);

if (isset(
$args['daemon'])) {
    if (!
function_exists('pcntl_fork')) {
       
fwrite(STDERR, "--daemon requires pcntl_fork, which is not available on this platform.\n");
        exit(
1);
    }
   
$logger->setLogToConsole(false);
   
daemonize();
} else {
   
setConsoleTitle('BinktermPHP FTP Daemon');
}

$pidDir = dirname($pidFile);
if (!
is_dir($pidDir)) {
    @
mkdir($pidDir, 0755, true);
}
@
file_put_contents($pidFile, (string)getmypid());
@
chmod($pidFile, 0644);

register_shutdown_function(static function () use ($pidFile): void {
    if (
file_exists($pidFile) && (int)trim((string)file_get_contents($pidFile)) === getmypid()) {
        @
unlink($pidFile);
    }
});

try {
   
$server = new FtpServer($host, $port, $publicHost, $passiveStart, $passiveEnd, $logger);
   
$server->start();

    while (
true) {
       
$read = $server->getReadSockets();
       
$write = $server->getWriteSockets();
       
$except = null;

        if (
$read === [] && $write === []) {
           
usleep(200000);
           
$server->tick();
            continue;
        }

       
$changed = @stream_select($read, $write, $except, 1);
        if (
$changed === false) {
           
usleep(200000);
        } elseif (
$changed > 0) {
            foreach (
$read as $socket) {
               
$server->handleReadableSocket($socket);
            }
            foreach (
$write as $socket) {
               
$server->handleWritableSocket($socket);
            }
        }

       
$server->tick();
    }
} catch (
Throwable $e) {
   
$logger->error('FTP daemon failed', ['error' => $e->getMessage()]);
   
fwrite(STDERR, "Error: " . $e->getMessage() . "\n");
    exit(
1);
}