# Functions for managing a virtual FTP server
$feature_depends{'ftp'} = [ 'dir', 'virt' ];
sub require_proftpd
{
return if ($require_proftpd++);
&foreign_require("proftpd", "proftpd-lib.pl");
}
# setup_ftp(&domain)
# Setup a virtual FTP server for some domain
sub setup_ftp
{
local $tmpl = &get_template($_[0]->{'template'});
&$first_print($text{'setup_proftpd'});
&require_proftpd();
# Get the template
local @dirs = &proftpd_template($tmpl->{'ftp'}, $_[0]);
# Add the directives
local $conf = &proftpd::get_config();
local $l = $conf->[@$conf - 1];
local $addfile = $proftpd::config{'add_file'} || $l->{'file'};
&lock_file($addfile);
local $lref = &read_file_lines($addfile);
local @lines = ( "<VirtualHost $_[0]->{'ip'}>" );
push(@lines, @dirs);
push(@lines, "</VirtualHost>");
push(@$lref, @lines);
&flush_file_lines($addfile);
&unlock_file($addfile);
# Create directory for FTP root
local ($fdir) = ($tmpl->{'ftp_dir'} || 'ftp');
local $ftp = "$_[0]->{'home'}/$fdir";
if (!-d $ftp) {
&system_logged("mkdir '$ftp' 2>/dev/null");
&system_logged("chmod 755 '$ftp'");
&system_logged("chown $_[0]->{'uid'}:$_[0]->{'ugid'} '$ftp'");
}
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_proftpd);
undef(@proftpd::get_config_cache);
# Add the FTP server user to the domain's group, so that the directory
# can be accessed in anonymous mode
local $ftp_user = &get_proftpd_user($_[0]);
if ($ftp_user) {
&add_user_to_domain_group($_[0], $ftp_user, 'setup_ftpuser');
}
}
# delete_ftp(&domain)
# Delete the virtual server from the ProFTPd config
sub delete_ftp
{
&require_proftpd();
local $conf = &proftpd::get_config();
&$first_print($text{'delete_proftpd'});
local ($virt, $vconf) = &get_proftpd_virtual($_[0]->{'ip'});
if ($virt) {
&lock_file($virt->{'file'});
local $lref = &read_file_lines($virt->{'file'});
splice(@$lref, $virt->{'line'}, $virt->{'eline'} - $virt->{'line'} + 1);
&flush_file_lines();
&unlock_file($virt->{'file'});
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_proftpd);
undef(@proftpd::get_config_cache);
}
else {
&$second_print($text{'delete_noproftpd'});
}
}
# modify_ftp(&domain, &olddomain)
# If the server has changed IP address, update the ProFTPd virtual server
sub modify_ftp
{
local $rv = 0;
&require_proftpd();
local $conf = &proftpd::get_config();
local ($virt, $vconf, $anon, $aconf) = &get_proftpd_virtual($_[1]->{'ip'});
return 0 if (!$virt);
&lock_file($virt->{'file'});
if ($_[0]->{'dom'} ne $_[1]->{'dom'}) {
# Update domain name in ProFTPd virtual server
&$first_print($text{'save_proftpd2'});
local $sname = &proftpd::find_directive_struct("ServerName", $vconf);
if ($sname) {
&proftpd::save_directive(
"ServerName", [ $_[0]->{'dom'} ], $vconf, $conf);
}
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'home'} ne $_[1]->{'home'} && $anon) {
# Update anonymous FTP directory in ProFTPd virtual server
&$first_print($text{'save_proftpd3'});
local $lref = &read_file_lines($anon->{'file'});
$lref->[$anon->{'line'}] =~ s/$_[1]->{'home'}/$_[0]->{'home'}/;
&flush_file_lines();
$rv++;
&$second_print($text{'setup_done'});
}
if ($_[0]->{'ip'} ne $_[1]->{'ip'}) {
# Update IP address in ProFTPd virtual server
&$first_print($text{'save_proftpd'});
local $lref = &read_file_lines($virt->{'file'});
$lref->[$virt->{'line'}] = "<VirtualHost $_[0]->{'ip'}>";
&flush_file_lines();
$rv++;
&$second_print($text{'setup_done'});
}
&unlock_file($virt->{'file'});
®ister_post_action(\&restart_proftpd) if ($rv);
return $rv;
}
# validate_ftp(&domain)
# Returns an error message if a domain's ProFTPd virtual server is not found
sub validate_ftp
{
local ($d) = @_;
local ($virt, $vconf, $anon, $aconf) = &get_proftpd_virtual($d->{'ip'});
return &text('validate_eftp', $d->{'ip'}) if (!$virt);
return undef;
}
# disable_ftp(&domain)
# Disable FTP for this server by adding a deny directive
sub disable_ftp
{
&$first_print($text{'disable_proftpd'});
&require_proftpd();
local ($virt, $vconf, $anon, $aconf) = &get_proftpd_virtual($_[0]->{'ip'});
if ($anon) {
&lock_file($anon->{'file'});
local @limit = &proftpd::find_directive_struct("Limit", $aconf);
local ($login) = grep { $_->{'words'}->[0] eq "LOGIN" } @limit;
if (!$login) {
local $lref = &read_file_lines($anon->{'file'});
splice(@$lref, $anon->{'eline'}, 0,
"<Limit LOGIN>", "DenyAll", "</Limit>");
&flush_file_lines();
}
&unlock_file($anon->{'file'});
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_proftpd);
}
else {
&$second_print($text{'delete_noproftpd'});
}
}
# enable_ftp(&domain)
# Enable FTP for this server by removing the deny directive
sub enable_ftp
{
&$first_print($text{'enable_proftpd'});
&require_proftpd();
local ($virt, $vconf, $anon, $aconf) = &get_proftpd_virtual($_[0]->{'ip'});
if ($virt) {
&lock_file($anon->{'file'});
local @limit = &proftpd::find_directive_struct("Limit", $aconf);
local ($login) = grep { $_->{'words'}->[0] eq "LOGIN" } @limit;
if ($login) {
local $lref = &read_file_lines($anon->{'file'});
splice(@$lref, $login->{'line'},
$login->{'eline'} - $login->{'line'} + 1);
&flush_file_lines();
}
&unlock_file($anon->{'file'});
&$second_print($text{'setup_done'});
®ister_post_action(\&restart_proftpd);
}
else {
&$second_print($text{'delete_noproftpd'});
}
}
# proftpd_template(text, &domain)
# Returns a suitably substituted ProFTPd template
sub proftpd_template
{
local $dirs = $_[0];
$dirs =~ s/\t/\n/g;
$dirs = &substitute_domain_template($dirs, $_[1]);
local @dirs = split(/\n/, $dirs);
return @dirs;
}
# check_proftpd_template([directives])
# Returns an error message if the default ProFTPd directives don't look valid
sub check_proftpd_template
{
local ($d, $gotuser, $gotgroup);
local @dirs = split(/\t+/, defined($_[0]) ? $_[0] : $config{'proftpd_config'});
foreach $d (@dirs) {
$d =~ s/#.*$//;
if ($d =~ /^\s*User\s+(\S+)$/i) {
defined(getpwnam($1)) ||
return &text('fcheck_euserex', "<tt>$1</tt>");
$gotuser++;
}
elsif ($d =~ /^\s*Group\s+(\S+)$/i) {
defined(getgrnam($1)) ||
return &text('fcheck_egroupex', "<tt>$1</tt>");
$gotgroup++;
}
}
$gotuser || return $text{'fcheck_euser'};
$gotgroup || return $text{'fcheck_egroup'};
return undef;
}
# restart_proftpd()
# Tell ProFTPd to re-read its config file. Does nothing if run from inetd
sub restart_proftpd
{
&require_proftpd();
local $conf = &proftpd::get_config();
local $st = &proftpd::find_directive("ServerType", $conf);
if (lc($st) ne "inetd") {
&$first_print($text{'setup_proftpdpid'});
# Call proftpd restart function
local $err = &proftpd::apply_configuration();
&$second_print($err ? &text('setup_proftpdfailed', $err)
: $text{'setup_done'});
return $err ? 0 : 1;
}
}
# get_proftpd_virtual(ip)
# Returns the list of configuration directives and the directive for the
# virtual server itself for some domain
sub get_proftpd_virtual
{
&require_proftpd();
local $conf = &proftpd::get_config();
local $v;
foreach $v (&proftpd::find_directive_struct("VirtualHost", $conf)) {
if ($v->{'words'}->[0] eq $_[0]) {
# Found it! Looks for
local $a = &proftpd::find_directive_struct("Anonymous", $v->{'members'});
if ($a) {
return ($v, $v->{'members'}, $a, $a->{'members'});
}
else {
return ($v, $v->{'members'});
}
}
}
return ();
}
# check_ftp_clash(&domain, [field])
# Returns 1 if a ProFTPd server already exists for some domain
sub check_ftp_clash
{
if (!$_[1] || $_[1] eq 'ip') {
local ($cvirt, $cconf) = &get_proftpd_virtual($_[0]->{'ip'});
return $cvirt ? 1 : 0;
}
return 0;
}
# backup_ftp(&domain, file)
# Save the virtual server's ProFTPd config as a separate file
sub backup_ftp
{
&$first_print($text{'backup_proftpdcp'});
local ($virt, $vconf) = &get_proftpd_virtual($_[0]->{'ip'});
if ($virt) {
local $lref = &read_file_lines($virt->{'file'});
local $l;
&open_tempfile(FILE, ">$_[1]");
foreach $l (@$lref[$virt->{'line'} .. $virt->{'eline'}]) {
&print_tempfile(FILE, "$l\n");
}
&close_tempfile(FILE);
&$second_print($text{'setup_done'});
return 1;
}
else {
&$second_print($text{'delete_noproftpd'});
return 0;
}
}
# restore_ftp(&domain, file)
# Update the virtual server's ProFTPd configuration from a file. Does not
# change the actual <Virtualhost> lines!
sub restore_ftp
{
&$first_print($text{'restore_proftpdcp'});
local ($virt, $vconf) = &get_proftpd_virtual($_[0]->{'ip'});
if ($virt) {
local $srclref = &read_file_lines($_[1]);
local $dstlref = &read_file_lines($virt->{'file'});
&lock_file($virt->{'file'});
splice(@$dstlref, $virt->{'line'}+1, $virt->{'eline'}-$virt->{'line'}-1,
@$srclref[1 .. @$srclref-2]);
if ($_[5]->{'home'} && $_[5]->{'home'} ne $_[0]->{'home'}) {
# Fix up any file-related directives
local $i;
foreach $i ($virt->{'line'} .. $virt->{'line'}+scalar(@$srclref)-1) {
$dstlref->[$i] =~ s/$_[5]->{'home'}/$_[0]->{'home'}/g;
}
}
&flush_file_lines();
&unlock_file($virt->{'file'});
&$second_print($text{'setup_done'});
}
else {
&$second_print($text{'delete_noproftpd'});
return 0;
}
®ister_post_action(\&restart_proftpd);
return 1;
}
# get_proftpd_log(ip)
# Given a virtual server IP, returns the path to its log file. If no IP is
# give, returns the global log file path.
sub get_proftpd_log
{
&require_proftpd();
if ($_[0]) {
# Find by IP
local ($virt, $vconf) = &get_proftpd_virtual($_[0]);
if ($virt) {
return &proftpd::find_directive("ExtendedLog", $vconf) ||
&proftpd::find_directive("TransferLog", $vconf);
}
}
else {
# Just return global log
local $conf = &proftpd::get_config();
local $global = &proftpd::find_directive_struct("Global", $conf);
return &proftpd::find_directive("TransferLog", $global->{'members'}) ||
&proftpd::find_directive("ExtendedLog", $global->{'members'}) ||
"/var/log/xferlog";
}
return undef;
}
# bandwidth_ftp(&domain, start, &bw-hash)
# Searches through FTP log files for records after some date, and updates the
# day counters in the given hash
sub bandwidth_ftp
{
local $log = &get_proftpd_log($_[0]->{'ip'});
if ($log) {
return &count_ftp_bandwidth($log, $_[1], $_[2], undef, "ftp");
}
else {
return $_[1];
}
}
# sysinfo_ftp()
# Returns the ProFTPd version
sub sysinfo_ftp
{
&require_proftpd();
$proftpd::site{'version'} ||= &proftpd::get_proftpd_version();
return ( [ $text{'sysinfo_proftpd'}, $proftpd::site{'version'} ] );
}
sub startstop_ftp
{
local ($typestatus) = @_;
&require_proftpd();
local $conf = &proftpd::get_config();
local $st = &proftpd::find_directive("ServerType", $conf);
if ($st eq 'inetd') {
# Running under inetd
return ( );
}
local $status;
if (defined($typestatus->{'proftpd'})) {
$status = $typestatus->{'proftpd'} == 1;
}
else {
$status = &proftpd::get_proftpd_pid();
}
if ($status) {
return ( { 'status' => 1,
'name' => $text{'index_fname'},
'desc' => $text{'index_fstop'},
'restartdesc' => $text{'index_frestart'},
'longdesc' => $text{'index_fstopdesc'} } );
}
else {
return ( { 'status' => 0,
'name' => $text{'index_fname'},
'desc' => $text{'index_fstart'},
'longdesc' => $text{'index_fstartdesc'} } );
}
}
# Call proftpd module's stop function
sub stop_service_ftp
{
&require_proftpd();
return &proftpd::stop_proftpd();
}
sub start_service_ftp
{
&require_proftpd();
return &proftpd::start_proftpd();
}
# show_template_ftp(&tmpl)
# Outputs HTML for editing ProFTPd related template options
sub show_template_ftp
{
local ($tmpl) = @_;
# ProFTPd directives
local @ffields = ( "ftp", "ftp_dir", "ftp_dir_def" );
local $ndi = &none_def_input("ftp", $tmpl->{'ftp'}, $text{'tmpl_ftpbelow'}, 1,
0, undef, \@ffields);
print &ui_table_row(&hlink($text{'tmpl_ftp'}, "template_ftp"),
$ndi."<br>\n".
&ui_textarea("ftp", $tmpl->{'ftp'} eq "none" ? "" :
join("\n", split(/\t/, $tmpl->{'ftp'})),
10, 60));
# Directory for anonymous FTP
print &ui_table_row(&hlink($text{'newftp_dir'}, "template_ftp_dir_def"),
&ui_opt_textbox("ftp_dir", $tmpl->{'ftp_dir'}, 20,
"$text{'default'} (<tt>ftp</tt>)",
$text{'newftp_dir0'})."<br>".
(" " x 3).$text{'newftp_dir0suf'});
}
# parse_template_ftp(&tmpl)
# Updates ProFTPd related template options from %in
sub parse_template_ftp
{
local ($tmpl) = @_;
# Save FTP directives
$tmpl->{'ftp'} = &parse_none_def("ftp");
if ($in{"ftp_mode"} == 2) {
local $err = &check_proftpd_template($in{'ftp'});
&error($err) if ($err);
if ($in{'ftp_dir_def'}) {
delete($tmpl->{'ftp_dir'});
}
else {
$in{'ftp_dir'} =~ /^\S+$/ && $in{'ftp_dir'} !~ /^\// &&
$in{'ftp_dir'} !~ /\.\./ || &error($text{'newftp_edir'});
$tmpl->{'ftp_dir'} = $in{'ftp_dir'};
}
}
}
sub links_ftp
{
local ($d) = @_;
local @rv;
if ($config{'avail_syslog'} && &get_webmin_version() >= 1.305) {
# Links to FTP log
local $lf = &get_proftpd_log($d->{'ip'});
if ($lf) {
local $param = &master_admin() ? "file"
: "extra";
push(@rv, { 'mod' => 'syslog',
'desc' => $text{'links_flog'},
'page' => "save_log.cgi?view=1&".
"$param=".&urlize($lf),
'cat' => 'logs',
});
}
}
return @rv;
}
# get_proftpd_user(&domain)
# Returns the Unix user that anonymous FTP access is done as. This is just
# taken from the User line in the template directives.
sub get_proftpd_user
{
local ($d) = @_;
local $tmpl = &get_template($d->{'template'});
local @dirs = &proftpd_template($tmpl->{'ftp'}, $d);
foreach my $l (@dirs) {
if ($l =~ /^\s*User\s+(\S+)/) {
return $1;
}
}
foreach my $u ("ftp", "anonymous") {
return $u if (defined(getpwnam($u)));
}
return undef;
}
$done_feature_script{'ftp'} = 1;
1;
syntax highlighted by Code2HTML, v. 0.9.1