###
###  Copyright 2000-2007 University of Illinois Board of Trustees
###  All rights reserved. 
###
###  AnonFTP.pm - anonymous FTP module for psgconf
###
###  Campus Information Technologies and Educational Services
###  University of Illinois at Urbana-Champaign
###


package PSGConf::Control::AnonFTP;

use strict;

use File::stat;
use Unix::Mknod qw(major minor);

use PSGConf::Action::MkDir;
use PSGConf::Action::MkNod;
use PSGConf::Action::Symlink;
use PSGConf::Action::CopyFile;
use PSGConf::Action::CreateFile;
use PSGConf::Action::GenerateFile::EnvFile;
use PSGConf::Action::GenerateFile::Literal;
use PSGConf::Action::GenerateFile::etc_passwd;
use PSGConf::Action::GenerateFile::ftpaccess;
use PSGConf::Action::Remove;
use PSGConf::Action::svcs::setprop;
use PSGConf::Data::Boolean;
use PSGConf::Data::Hash;
use PSGConf::Data::List;
use PSGConf::Data::String;

use PSGConf::Control::PAM qw(_add_pam);
use PSGConf::Control::Packages qw(_add_pkgs);
use PSGConf::Control::syslog qw(_add_syslog);

###############################################################################
###  policy methods
###############################################################################

### ftp server name should default to hostname
sub _policy_default_servername
{
	my ($self, $psgconf) = @_;
	my ($tmp);

	return
		if ($psgconf->data_obj('anon_ftp_enable')->equals('false'));

	$psgconf->data_obj('anon_ftp_server_name')->set(
		$psgconf->data_obj('hostname')->get()
	) if (!defined $psgconf->data_obj('anon_ftp_server_name')->get());

	$tmp = 'ftpadmin@' . $psgconf->data_obj('anon_ftp_server_name')->get();
	$psgconf->data_obj('anon_ftp_options')->insert(
		{ 'email' => $tmp }
	) if (!defined $psgconf->data_obj('anon_ftp_options')->find('email'));
}


### uploads should be disabled by default
sub _policy_default_uploads
{
	my ($self, $psgconf) = @_;
	my ($tmp);

	return
		if ($psgconf->data_obj('anon_ftp_enable')->equals('false'));

	### no uploading allowed by default
	$tmp = $psgconf->data_obj('anon_ftp_dir')->get() . ' *';
	$psgconf->data_obj('anon_ftp_upload')->insert(
		{ $tmp => 'no' }
	) if (!defined $psgconf->data_obj('anon_ftp_upload')->find($tmp));
}


### create ftp user
sub _policy_add_user
{
	my ($self, $psgconf) = @_;
	my ($user, $group);

	return
		if ($psgconf->data_obj('anon_ftp_enable')->equals('false'));

	$user = $psgconf->data_obj('anon_ftp_user')->get();
	$group = $psgconf->data_obj('anon_ftp_group')->get();

	$psgconf->data_obj('group_info')->insert(
		{ $group => {} }
	) if (!defined $psgconf->data_obj('group_info')->find($group));

	$psgconf->data_obj('user_info')->insert(
		{ $user => {
			group	=> $group,
			passwd	=> $psgconf->data_obj('passwd_token')->get(),
			home		=> $psgconf->data_obj('anon_ftp_dir')->get(),
			gecos	=> 'Anonymous FTP',
			shell	=> '/bin/false',
			home_mode	=> 0555
			}
		}
	) if (!defined $psgconf->data_obj('user_info')->find($user));
}


### create ftp aliases
sub _policy_add_sendmail_aliases
{
	my ($self, $psgconf) = @_;
	my ($user);

	return
		if ($psgconf->data_obj('anon_ftp_enable')->equals('false'));

	$user = $psgconf->data_obj('anon_ftp_user')->get();

	$psgconf->data_obj('sendmail_aliases')->insert(
		{ $user => 'root' }
	) if (!defined $psgconf->data_obj('sendmail_aliases')->find($user));

	$psgconf->data_obj('sendmail_aliases')->insert(
		{ 'ftpadmin' => $user }
	) if (!$psgconf->data_obj('sendmail_aliases')->exists('ftpadmin'));
}


### add PAM configs
sub _policy_add_pam
{
	my ($self, $psgconf) = @_;
	my ($service, $lines);

	$service = ( $psgconf->data_obj('platform')->match('linux'))
		? 'ftp'
		: 'ftpd';

	$self->{pam_name} = $service;
	$self->{pam_conf} = $psgconf->data_obj('anon_ftp_pam_conf')->get();

	$self->_add_pam($psgconf)
		if ($psgconf->data_obj('anon_ftp_pam_conf')->count());
}

### add syslog entry
sub _policy_add_syslog
{
	my ($self, $psgconf) = @_;

	$self->_add_syslog($psgconf)
		if (exists($self->{syslog}));
}

### add inetd entry
sub _policy_add_inetd_entry
{
	my ($self, $psgconf) = @_;

	return
		if ($psgconf->data_obj('anon_ftp_enable')->equals('false'));

	$psgconf->data_obj('inetd')->insert(
		{ 'ftp/tcp' => {
			'server' => $psgconf->data_obj('anon_ftp_ftpd_path')->get()
			}
		}
	) if ( ! $psgconf->data_obj('platform')->match('solaris10') );

	$psgconf->data_obj('inetd')->insert(
		{ 'ftp/tcp' => {
			'server_args' => 
				'-a -d -l -r ' . $psgconf->data_obj('anon_ftp_dir')->get()
			} 
		}
	) if ( $psgconf->data_obj('anon_ftp_use_vsftpd')->equals('false') && 
			! $psgconf->data_obj('platform')->match('solaris10') );
}

sub _enable_rc_scripts
{
     my ($self, $psgconf) = @_;

     $psgconf->data_obj('rc_scripts')->insert(
		{ 'ftp' => { 'state' => 'enable' }}
	) if ( $psgconf->data_obj('anon_ftp_enable')->equals('true')
		&& defined $psgconf->data_obj('rc_scripts')->find('ftp'));
}



### add TCP wrappers entry
sub _policy_add_tcpwrapper_entry
{
	my ($self, $psgconf) = @_;
	my ($cmd);

	return
		if ($psgconf->data_obj('anon_ftp_enable')->equals('false'));

	$cmd = ($psgconf->data_obj('anon_ftp_use_vsftpd')->equals('true'))
			? 'vsftpd'
			: 'in.ftpd';

	$psgconf->data_obj('tcp_wrappers')->insert_row(
		{ 1 => 'all' },
		[ $cmd, 'all', 'allow' ]
	)
		if (! $psgconf->data_obj('tcp_wrappers')->find_row(
					{ 0 => qr/\b$cmd\b/ }));
}


###############################################################################
###  decide() method
###############################################################################

sub decide
{
	my ($self, $psgconf) = @_;
	my ($file, $dest, $mode, $uid, $gid, $conversions, $userinfo, $user);
	$mode = PSGConf::Data::Integer->new();

	### if we're not enabled, remove ftpaccess and return
	if ($psgconf->data_obj('anon_ftp_enable')->equals('false'))
	{
		$psgconf->register_actions(
			PSGConf::Action::Remove->new(
				name	=> $psgconf->data_obj('anon_ftp_cfg_dir')->get() . '/ftpaccess'
			)
		);
		return;
	}

     ### add fake /etc/passwd file with just the anon ftp account.
	$user = $psgconf->data_obj('anon_ftp_user')->get();
	%{$userinfo->{'ftp'}} = %{$psgconf->data_obj('user_info')->find($user)};
	$userinfo->{'ftp'}->{home} = '/';

	map {
		$conversions .= $_
				. ":"
				. $psgconf->data_obj('anon_ftp_conversions')->find($_)
				. "\n"
	} keys %{$psgconf->data_obj('anon_ftp_conversions')->get()};

	$uid = ( defined $psgconf->data_obj('user_info')->find($psgconf->data_obj('anon_ftp_user')->get()))?
		$psgconf->data_obj('user_info')->find($psgconf->data_obj('anon_ftp_user')->get())->{'uid'}:
		-1;
	$gid = ( defined $psgconf->data_obj('user_info')->find($psgconf->data_obj('anon_ftp_user')->get()))?
		$psgconf->data_obj('user_info')->find($psgconf->data_obj('anon_ftp_user')->get())->{'gid'}:
		-1;

	$psgconf->register_actions(
		(map {
			$file = $psgconf->data_obj('anon_ftp_chroot_files')->find($_);
			if ( exists $file->{location} ) {
				$dest = $psgconf->data_obj('anon_ftp_dir')->get() . $file->{location};
			} else {
				$dest = $psgconf->data_obj('anon_ftp_dir')->get() . $_;
			}
			$uid = ( exists $file->{owner} )? 
					(( defined $psgconf->data_obj('user_info')->find($file->{owner}) )? 
						$psgconf->data_obj('user_info')->find($file->{owner})->{uid}: 0): 0;
			$gid = ( exists $file->{group} )? 
					(( defined $psgconf->data_obj('user_info')->find($file->{group}) )? 
						$psgconf->data_obj('user_info')->find($file->{group})->{gid}: 0): 0;

			### If the source file does not exist, assume you
			### want to create a directory.
			if ( -d $_ || ! -e $_ ) {
				$mode->set((exists $file->{mode})?  $file->{mode}: 0111);
				PSGConf::Action::MkDir->new(
					name		=> $dest,
					mode		=> $mode->get(),
					uid		=> $uid,
					gid		=> $gid
				);
			} elsif ( -l $_ ) {
				PSGConf::Action::Symlink->new(
					name		=> $dest,
					link_to	=> $_
				);
			} elsif ( -b $_ || -c $_ ) {
				my ($st) = stat $_;
				$mode->set((exists $file->{mode})?  $file->{mode}: 0644);
				PSGConf::Action::MkNod->new(
					name		=> $dest,
					mode		=> $mode->get(),
					type		=> (-b $_)? 'b': 'c',
					major	=> major($st->rdev),
					minor	=> minor($st->rdev)
				);
			} elsif ( -f $_ ) {
				$mode->set((exists $file->{mode})?  $file->{mode}: 0444);
				PSGConf::Action::CopyFile->new(
					name		=> $dest,
					copy_from	=> $_,
					mode		=> $mode->get(),
					uid		=> $uid,
					gid		=> $gid
				);
			}
		} keys %{$psgconf->data_obj('anon_ftp_chroot_files')->get()}),
		PSGConf::Action::GenerateFile::etc_passwd->new(
			'name'         => $psgconf->data_obj('anon_ftp_dir')->get() . '/etc/passwd',
			'comment_str'  => undef,
			'mode'         => 0444,
			'sort_func'	=> sub { $a cmp $b },
			'user_info'    => $userinfo,
			'passwd_token' => $psgconf->data_obj('passwd_token')->get(),
			'add_passwords'     => ($psgconf->data_obj('platform')->match('hpux'))
		),
		PSGConf::Action::CreateFile->new(
			'name'         => $psgconf->data_obj('anon_ftp_dir')->get() . '/var/adm/wtmpx',
		)
	);


	### Now take care of the vsftpd/wu-ftpd specific files
	if ( $psgconf->data_obj('anon_ftp_use_vsftpd')->equals('true') ) {
		$psgconf->register_actions(
			PSGConf::Action::GenerateFile::EnvFile->new(
				name		=> '/etc/vsftpd/vsftpd.conf',
				vars		=> $psgconf->data_obj('anon_ftp_vsftpd_options')->get()
			),
			PSGConf::Action::GenerateFile::Literal->new(
				name		=> '/etc/vsftpd.ftpusers',
				content	=> join ("\n", @{$psgconf->data_obj('anon_ftp_users')->get()}) . "\n"
			),
			PSGConf::Action::GenerateFile::Literal->new(
				name		=> '/etc/vsftpd.user_list',
				content	=> join ("\n", @{$psgconf->data_obj('anon_ftp_users')->get()}) . "\n"
			)
		);
	} else {
		$psgconf->register_actions(
			PSGConf::Action::GenerateFile::ftpaccess->new(
				name		=> $psgconf->data_obj('anon_ftp_cfg_dir')->get() . '/ftpaccess',
				description	=> 'FTP server configuration file',
				classes		=> $psgconf->data_obj('anon_ftp_class')->get(),
				autogroups	=> $psgconf->data_obj('anon_ftp_autogroup')->get(),
				limits		=> $psgconf->data_obj('anon_ftp_limit')->get(),
				options		=> $psgconf->data_obj('anon_ftp_options')->get(),
				messages		=> $psgconf->data_obj('anon_ftp_message')->get(),
				readmes		=> $psgconf->data_obj('anon_ftp_readme')->get(),
				uploads		=> $psgconf->data_obj('anon_ftp_upload')->get(),
				literal_config	=> $psgconf->data_obj('anon_ftp_literal')->get()
			),
			PSGConf::Action::GenerateFile::Literal->new(
				name		=> '/etc/ftpusers',
				content	=> join ("\n", @{$psgconf->data_obj('anon_ftp_users')->get()}) . "\n"
			),
			(map {
				PSGConf::Action::GenerateFile::Literal->new(
					name			=> $psgconf->data_obj('anon_ftp_dir')->get() .
									$psgconf->data_obj('anon_ftp_cfg_dir')->get() .
									'/ftp-banners/' . $_,
					comment_str	=> undef,
					content		=> $psgconf->data_obj('anon_ftp_banners')->find($_)
				)
			} sort keys %{$psgconf->data_obj('anon_ftp_banners')->get()}),
			PSGConf::Action::GenerateFile::Literal->new(
				name		=> $psgconf->data_obj('anon_ftp_dir')->get() .
							$psgconf->data_obj('anon_ftp_cfg_dir')->get() .
							'/ftpconversions',
				content	=> $conversions
			)
		);
	}


	if ( $psgconf->data_obj('platform')->match('solaris10') ) {
		$psgconf->register_actions(
			PSGConf::Action::svcs::setprop->new(
				name           => 'enable FTP startup options',
				FMRI           => 'svc:/network/ftp',
				property       => 'inetd_start/exec',
				value          => 
					'"' . $psgconf->data_obj('anon_ftp_ftpd_path')->get() .
					' -a -d -l -r ' .  $psgconf->data_obj('anon_ftp_dir')->get() . '"'
			)
		);
	}
}


###############################################################################
###  Constructor
###############################################################################

sub new
{
	my ($class, $psgconf) = @_;
	my ($self);

	$self = {};
	bless($self, $class);

	### So that _add_pkgs knows which directives to look at
	$self->{name} = 'anon_ftp';
	$self->{enable} = $self->{name} . '_enable';
	$self->{packages} = $self->{name} . '_packages';

	### So that _add_syslog knows to add syslog file support.
	### If we are running on Linux or the *BSD's then use ftp facility,
	### otherwise the system logs by default to DAEMON
	if ( $psgconf->data_obj('platform')->match('linux|bsd')) {
		$self->{syslog} = 'ftp';
		$self->{facility} = $self->{syslog} . '.info';
	}

	$psgconf->register_data(
		anon_ftp_autogroup	=> PSGConf::Data::Hash->new(),
		anon_ftp_banners	=> PSGConf::Data::Hash->new(),
		anon_ftp_conversions	=> PSGConf::Data::Hash->new(),
		anon_ftp_class		=> PSGConf::Data::List->new(),
		anon_ftp_user		=> PSGConf::Data::String->new(
						'value' => 'ftp'
					),
		anon_ftp_users		=> PSGConf::Data::List->new(),
		anon_ftp_group		=> PSGConf::Data::String->new(
						'value' => 'ftp'
					),
		anon_ftp_dir		=> PSGConf::Data::String->new(
						'value_abspath' => 1,
						'value' => '/services/ftp'
					),
		anon_ftp_enable		=> PSGConf::Data::Boolean->new(
						'value' => 'false'
					),
		anon_ftp_packages		=> PSGConf::Data::List->new(),
		anon_ftp_limit		=> PSGConf::Data::List->new(),
		anon_ftp_literal	=> PSGConf::Data::String->new(),
		anon_ftp_cfg_dir	=> PSGConf::Data::String->new(
						'value_abspath' => 1,
						'value' => '/etc'
					),
		anon_ftp_ftpd_path	=> PSGConf::Data::String->new(
						'value_abspath' => 1,
						'value' => '/usr/sbin/in.ftpd'
					),
		anon_ftp_message	=> PSGConf::Data::Hash->new(),
		anon_ftp_options	=> PSGConf::Data::Hash->new(),
		anon_ftp_vsftpd_options	=> PSGConf::Data::Hash->new(),
		anon_ftp_chroot_files	=> PSGConf::Data::Hash->new(
						value_type => 'HASH'
					),
		anon_ftp_readme		=> PSGConf::Data::Hash->new(),
		anon_ftp_server_name	=> PSGConf::Data::String->new(),
		anon_ftp_upload		=> PSGConf::Data::Hash->new(
						key_abspath => 1
					),
		anon_ftp_pam_conf		=> PSGConf::Data::List->new(),
		anon_ftp_use_vsftpd		=> PSGConf::Data::Boolean->new(
						value => 'false'
					)
	);

	$psgconf->register_policy($self,
		anon_ftp_default_servername
				=> '_policy_default_servername',
		anon_ftp_default_uploads
				=> '_policy_default_uploads',
		anon_ftp_add_user
				=> '_policy_add_user',
		anon_ftp_add_sendmail_aliases
				=> '_policy_add_sendmail_aliases',
		anon_ftp_add_inetd_entry
				=> '_policy_add_inetd_entry',
		anon_ftp_add_tcpwrapper_entry
				=> '_policy_add_tcpwrapper_entry',
		anon_ftp_add_syslog
				=> '_policy_add_syslog',
		anon_ftp_add_packages
				=> '_add_pkgs',
		anon_ftp_add_pam
				=> '_policy_add_pam',
		anon_ftp_enable_rc_scripts
				=> '_enable_rc_scripts'
	);

	return $self;
}


###############################################################################
###  documentation
###############################################################################

1;

__END__

=head1 NAME

PSGConf::Control::AnonFTP - psgconf control class for anonymous FTP

=head1 SYNOPSIS

In F<psgconf_modules>:

  Control PSGConf::Control::AnonFTP

=head1 DESCRIPTION

The B<PSGConf::Control::AnonFTP> module provides a B<psgconf> control
object for configuring anonymous FTP using wu-ftpd.  It provides the
following methods:

=over 4

=item new()

The constructor.  Its parameter is a reference to the B<PSGConf>
object.  It registers the following data objects:

=over 4

=item I<anon_ftp_ftpd_path>

A B<PSGConf::Data::String> object containing the absolute path to the 
wu-ftpd binary.  The default is F</usr/local/sbin/in.ftpd>.

=item I<anon_ftp_autogroup>

A B<PSGConf::Data::Hash> object that specifies autogroup mappings.  The
hash key is the group name, and the value is a space-seperated list of
classes.

=item I<anon_ftp_banners>

A B<PSGConf::Data::Hash> object that specifies what FTP banner files
need to be created.  The hash key is the name of the file, and the value
is the file's contents.

=item I<anon_ftp_class>

A B<PSGConf::Data::List> object that contains a list of classes to
define in F<ftpaccess>.

=item I<anon_ftp_dir>

A B<PSGConf::Data::String> object that sets the path to the anonymous
FTP root.  It must be an absolute path.  If not set, it defaults to
F</services/ftp>.

=item I<anon_ftp_user>

A B<PSGConf::Data::String> object that sets the login for the ftpd to
run as.  Defaults to C<ftp>.

=item I<anon_ftp_group>

A B<PSGConf::Data::String> object that sets the group for the ftpd to
run as.  Defaults to C<ftp>.

=item I<anon_ftp_users>

A B<PSGConf::Data::List> object that defines the contents of the ftpusers file.

=item I<anon_ftp_enable>

A B<PSGConf::Data::Boolean> object that enables anonymous FTP.  Default
is off.

=item I<anon_ftp_packages>

A B<PSGConf::Data::List> object lists what packages to install.

=item I<anon_ftp_limit>

A B<PSGConf::Data::List> object that contains a list of limits to define
in F<ftpaccess>.

=item I<anon_ftp_conversions>

A B<PSGConf::Data::Hash> object containing the contents of the
F<ftpconversions> file, where the key is the first 4 fields of
the ftpconversions file (the Strip prefix, Strip postfix, Addon
prefix, and Addon postfix) and the value is the rest of the line
in the file.

=item I<anon_ftp_cfg_dir>

A B<PSGConf::Data::String> object that contains the location of all the
ftpd configuration files.  Defaults to F</etc>.

=item I<anon_ftp_literal>

A B<PSGConf::Data::String> object that contains literal text to add to
F<ftpaccess>.  (This is a nasty hack, and should be avoided.)

=item I<anon_ftp_message>

A B<PSGConf::Data::Hash> object that specifies the C<message> directives
to add to F<ftpaccess>.  The hash key is the name of the message
file, and the value sets when the directive is triggered.

=item I<anon_ftp_options>

A B<PSGConf::Data::Hash> object that contains miscellaneous options for
F<ftpaccess>.  The hash key is the option name, and the value is
the option value.

=item I<anon_ftp_vsftpd_options>

A B<PSGConf::Data::Hash> object that contains the contents of the
F<vsftpd.conf> file.

=item I<anon_ftp_chroot_files>

A B<PSGConf::Data::Hash> object listing what files are needed to setup
the chroot'ed ftp environment.  The key is the file on the system,
and points to a hash with the following sub keys:

=item I<anon_ftp_pam_conf>

A B<PSGConf::Data::List> object to set I<pam_conf> (provided by
B<PSGConf::Control::PAM>), to configure PAM for ftp server support.

=item I<anon_ftp_use_vsftpd>

A B<PSGConf::Data::Boolean> object to tell the control object to configure
using C<vsftpd> instead of C<wu-ftpd>.  Defaults to off.

=over 4

=item I<location>

The location to put the file under I<anon_ftp_dir>.

=item I<mode>

The mode the file should have.

=back

=item I<anon_ftp_readme>

A B<PSGConf::Data::Hash> object that specifies the C<readme> directives
to add to F<ftpaccess>.  The hash key is the name of the F<README>
file, and the value sets when the directive is triggered.

=item I<anon_ftp_server_name>

A B<PSGConf::Data::String> object that contains the advertised server
name of the FTP server.  If not set, it defaults to the value of the
I<hostname> object, which is supplied by B<PSGConf::Control::Core>.

=item I<anon_ftp_upload>

A B<PSGConf::Data::Hash> object that specifies the C<upload> directives
to add to F<ftpaccess>.  The hash key is a string containing the
root directory, followed by a space, followed by the list of relative
directories (or a C<*>).  The value is C<no>, or C<yes> followed by
optional owner and permissions for the uploaded files.

=back

In addition, the constructor registers the following policy methods:

=over 4

=item I<anon_ftp_default_servername>

If the I<anon_ftp_server_name> object is unset, it is set to the value
of the I<hostname> object, which is provided by
B<PSGConf::Control::Core>.

If the I<anon_ftp_options> object does not contain a setting for the
C<email> option, it is set to C<ftpadmin@> followed by the value of the
I<anon_ftp_server_name> object.

=item I<anon_ftp_default_uploads>

If the I<anon_ftp_upload> object is unset, an entry is added for root
directory I<anon_ftp_dir>, subdirectory C<*>, value C<no>.

=item I<anon_ftp_add_user>

Adds entries to the I<user_info> and I<group_info> Data objects (supplied
by the B<PSGConf::Control::Users> module) for user and group C<ftp>.

=item I<anon_ftp_add_pam>

Adds entries from I<anon_ftp_pam_conf> to the I<pam_conf> Data object
(supplied by the B<PSGConf::Control::PAM> module) for the C<ftp> service.

=item I<anon_ftp_add_sendmail_aliases>

If mail aliases for C<ftp> and C<ftpadmin> do not exist, aliases are
created that point to C<root>.  This is done using the
I<sendmail_aliases> Data object, which is provided by the
B<PSGConf::Control::sendmail> module.

=item I<anon_ftp_enable_rc_scripts>

If there is an entry in the I<rc_scripts> hash for I<ftp>, then go ahead
and enable it.

=item I<anon_ftp_add_inetd_entry>

If there is no entry in the I<inetd> data object (provided by
B<PSGConf::Control::inetd>) for C<ftp/tcp>, create one that invokes the
binary specified by the I<anon_ftp_ftpd_path> data object with the
arguments C<-a -d -l -r anon_ftp_dir>.

=item I<anon_ftp_add_tcpwrapper_entry>

If the I<tcp_wrappers> object has no entry for C<in.ftpd>, add one with
the values C<in.ftpd>, C<all>, C<allow>.  This is done using
the I<tcp_wrappers> object, which is provided by
B<PSGConf::Control::TCPWrappers>.

=item I<anon_ftp_add_syslog>

Under Linux and *BSD, adds an F<ftp> entry to the I<syslog> data object
(supplied by B<PSGConf::Control::syslog>).  (On other platforms, there
is no C<ftp> syslog facility, so F<wu-ftpd> logs to the C<daemon>
facility.)

=item I<anon_ftp_add_packages>

Adds F<wu-ftpd> to the list of requested packages.  This
uses the I<pkg_install_list> config object supplied by
B<PSGConf::Control::Packages>.

=back

=item decide()

Instantiates and registers the following action objects:

=over 4

=item *

If I<anon_ftp_enable> is not set, it registers a
B<PSGConf::Action::Remove> object to remove F<ftpaccess> and
returns immediately.

=item *

Instantiates a B<PSGConf::Action::GenerateFile::ftpaccess> object to
create F<ftpaccess>.

=item *

Instantiates B<PSGConf::Action::GenerateFile::Literal> objects to create
each of the files listed in the I<anon_ftp_banners> object.  The files
are created in the F<anon_ftp_dir/ftp-banners> directory.

=item *

Instantiates B<PSGConf::Action::GenerateFile::Literal> objects to create
the ftpconversions file.

=item *

Instantiates B<PSGConf::Action::CreateFile> objects to create
the F<anon_ftp_dir/var/adm/wtmp> file.

=item *

Instantiates B<PSGConf::Action::GenerateFile::etc_passwd> objects to create
the I<anon_ftp_dir>/etc/passwd file.

=item *

If we are running on Solaris10, instantiates 
B<PSGConf::Action::svcs::setprop> objects to set the SMF properties for
ftpd to C<-a -d -l -r anon_ftp_dir>.

=item *

Instantiates B<PSGConf::Action::CopyFile> object for each file listed in
I<anon_ftp_chroot_files> object.  Instantiates B<PSGConf::Action::SymLink>
for each symlink, B<PSGConf::Action::MkNod> for each block or character
special file and B<PSGConf::Action::MkDir> for each directory or if the
file does not exist.

=back

=back

=head1 SEE ALSO

L<perl>

ftpaccess(5)

L<File::stat>

L<Unix::Mknod>

L<PSGConf>

L<PSGConf::Action::CopyFile>

L<PSGConf::Action::MkDir>

L<PSGConf::Action::MkNod>

L<PSGConf::Action::Symlink>

L<PSGConf::Action::GenerateFile::ftpaccess>

L<PSGConf::Action::GenerateFile::etc_passwd>

L<PSGConf::Action::GenerateFile::EnvFile>

L<PSGConf::Action::GenerateFile::Literal>

L<PSGConf::Action::Remove>

L<PSGConf::Action::CreateFile>

L<PSGConf::Control::inetd>

L<PSGConf::Control::Packages>

L<PSGConf::Control::sendmail>

L<PSGConf::Control::syslog>

L<PSGConf::Control::PAM>

L<PSGConf::Control::Packages>

L<PSGConf::Control::TCPWrappers>

L<PSGConf::Control::Users>

L<PSGConf::Data::Boolean>

L<PSGConf::Data::Hash>

L<PSGConf::Data::List>

L<PSGConf::Data::String>

L<psgconf-intro>

=cut



syntax highlighted by Code2HTML, v. 0.9.1