### ### Copyright 2000-2007 University of Illinois Board of Trustees ### All rights reserved. ### ### sendmail.pm - sendmail module for psgconf ### ### Campus Information Technologies and Educational Services ### University of Illinois at Urbana-Champaign ### package PSGConf::Control::sendmail; use strict; use File::Basename; use File::stat; use PSGConf::Action::GenerateFile::sendmail_aliases; use PSGConf::Action::GenerateFile::sendmail_mc; use PSGConf::Action::GenerateFile::sendmail_cf; use PSGConf::Action::GenerateFile::sendmail_trusted_users; use PSGConf::Action::GenerateFile::sendmail_map; use PSGConf::Action::MkDir; use PSGConf::Action::RunCommand; use PSGConf::Action::RestartDaemon; use PSGConf::Data::Boolean; use PSGConf::Data::List; use PSGConf::Data::Hash; use PSGConf::Data::Integer; use PSGConf::Data::String; use PSGConf::Control::Packages qw(_add_pkgs); use PSGConf::Control::syslog qw(_add_syslog); ############################################################################### ### plugin function for PSGConf::Action::RunCommand ############################################################################### sub _check_map { my ($self, $psgconf) = @_; my ($action, $ext, $st, $st2, $found, $cnt); $action = $psgconf->get_action($self->{filename}); return 1 if (defined($action) && $action->changed()); $st = stat $self->{filename}; ### ### Originally I was going to use the DATABASE_MAP_TYPE to determine ### which extension to look for against the alias file, but I found ### some sendmail installations would use one format for most maps but ### the aliases file was a different format. Now I am going to check ### and make sure that I find at least one file that is newer. If ALL ### of the map files that we find are out of date, then run newaliases. ### $found = $cnt = 0; foreach $ext ( 'db', 'dir', 'pag') { if ( -f $self->{filename} . '.' . ${ext} ) { $found++; $st2 = stat $self->{filename} . '.' . ${ext}; ### If our map file is newer than the ### .dir/.pag or .db files, then cause the ### map command to be rerun. $cnt++ if ( $st->mtime > $st2->mtime ); } } return ($cnt == $found)? 1: 0; } ############################################################################### ### policy methods ############################################################################### ### set up rc scripts sub _policy_add_rc_scripts { my ($self, $psgconf) = @_; my ($start_cmd); ### FIXME: Put this into the config files instead ### set start command $start_cmd = $psgconf->data_obj('sendmail_path')->get() . ' -bd -q20m \ && ' . $psgconf->data_obj('rc_echo')->get() . ' " (MTA)\c" if [ -s ' . $psgconf->data_obj('sendmail_cf_dir')->get() . '/submit.cf ]; then ' . $psgconf->data_obj('sendmail_path')->get() . ' -L sm-msp -Ac -q20m \ && ' . $psgconf->data_obj('rc_echo')->get() . ' ", (MSP)\c" fi'; $start_cmd =~ s/-q20m/-qp/ if ($psgconf->data_obj('sendmail_use_persistent_queue_runners')->equals('true')); $psgconf->data_obj('rc_scripts')->insert( { 'sendmail' => { 'state' => 'enable', 'start_cmd' => $start_cmd } } ) if ( $psgconf->data_obj('sendmail_enable')->equals('true') ); } ### add TCP wrappers entry sub _policy_add_tcpwrapper_entry { my ($self, $psgconf) = @_; ### NOTE: Adding sendmail wrappers for all platforms, but I think ### the only ones that need it are FreeBSD, RHEL3+, and Solaris 10 return if ( $psgconf->data_obj('sendmail_enable')->equals('false') ); $psgconf->data_obj('tcp_wrappers')->insert_row( { 1 => 'all' }, [ 'sendmail', 'ALL', 'allow' ] ) if (! $psgconf->data_obj('tcp_wrappers')->find_row( { 0 => qr/\bsendmail\b/ })); } ### add smmsp user and group sub _policy_add_user { my ($self, $psgconf) = @_; return if ($psgconf->data_obj('sendmail_enable')->equals('false')); $psgconf->data_obj('group_info')->insert( { $psgconf->data_obj('sendmail_msa_group')->get() => { 'recommended_gid' => $psgconf->data_obj('sendmail_msa_gid')->get() } } ) if (!defined $psgconf->data_obj('group_info')->find( $psgconf->data_obj('sendmail_msa_group')->get())); $psgconf->data_obj('user_info')->insert( { $psgconf->data_obj('sendmail_msa_user')->get() => { 'recommended_uid' => $psgconf->data_obj('sendmail_msa_uid')->get(), 'passwd' => $psgconf->data_obj('passwd_token')->get(), 'group' => $psgconf->data_obj('sendmail_msa_group')->get(), 'gecos' => 'sendmail message submission program', 'home' => '/var/spool/clientmqueue' } } ) if (!defined $psgconf->data_obj('user_info')->find( $psgconf->data_obj('sendmail_msa_user')->get())); } ### set confPID_FILE option sub _policy_default_pidfile { my ($self, $psgconf) = @_; return if ($psgconf->data_obj('sendmail_enable')->equals('false')); $psgconf->data_obj('sendmail_options')->insert( { 'confPID_FILE' => $psgconf->data_obj('pidfile_dir')->get() . '/sendmail.pid' } ) if (!defined $psgconf->data_obj('sendmail_options')->find('confPID_FILE')); } ### set default trusted-users file sub _policy_default_ct_file { my ($self, $psgconf) = @_; return if ($psgconf->data_obj('sendmail_enable')->equals('false')); if (!defined $psgconf->data_obj('sendmail_options')->find('confCT_FILE') && $psgconf->data_obj('sendmail_trusted_users')->count()) { $psgconf->data_obj('sendmail_options')->insert( { 'confCT_FILE' => '-o ' . $psgconf->data_obj('sendmail_cf_dir')->get() . '/trusted-users' } ); $psgconf->data_obj('sendmail_features')->insert( { 'use_ct_file' => undef } ); } } my %_default_aliases = ( postmaster => 'root', 'MAILER-DAEMON' => 'postmaster', nobody => '/dev/null' ); ### set default aliases sub _policy_default_aliases { my ($self, $psgconf) = @_; my ($aliases); $aliases = $psgconf->data_obj('sendmail_aliases')->get(); map { $psgconf->data_obj('sendmail_aliases')->insert( { $_ => $_default_aliases{$_} } ) if (!defined $psgconf->data_obj('sendmail_aliases')->find($_)); } keys %_default_aliases; } ############################################################################### ### decide() method ############################################################################### sub _add_queue_dirs { my ($self, $psgconf, $qdir, $uid, $gid) = @_; $psgconf->register_actions( PSGConf::Action::MkDir->new( name => $qdir, mode => $psgconf->data_obj('sendmail_queue_mode')->get(), uid => $uid, gid => $gid ), ($psgconf->data_obj('sendmail_queue_qf_subdirs')->equals('true') ? (map { PSGConf::Action::MkDir->new( name => "$qdir/$_", mode => $psgconf->data_obj('sendmail_queue_mode')->get(), uid => $uid, gid => $gid ) } qw(qf df xf)) : () ), ); } sub decide { my ($self, $psgconf) = @_; my ($smopts, $smfeatures, $qgrps); my ($alias_file, $uid, $gid, $smmsp_gid, $smmsp_uid); my ($platform, $access_map, $mailertable, $map_type, $map_path); my ($num_queues, $qdir); return if ($psgconf->data_obj('sendmail_enable')->equals('false')); $smopts = $psgconf->data_obj('sendmail_options')->get(); $smfeatures = $psgconf->data_obj('sendmail_features')->get(); $qgrps = $psgconf->data_obj('sendmail_queue_groups')->get(); $num_queues = $psgconf->data_obj('sendmail_num_queues_per_group')->get(); $alias_file = $smopts->{'ALIAS_FILE'}; $alias_file =~ s|,.*||; $smmsp_uid = $psgconf->data_obj('user_info')->find( $psgconf->data_obj('sendmail_msa_user')->get() )->{'uid'}; $smmsp_gid = $psgconf->data_obj('group_info')->find( $psgconf->data_obj('sendmail_msa_group')->get() )->{'gid'}; $uid = ( defined $psgconf->data_obj('user_info')->find($psgconf->data_obj('sendmail_mta_user')->get()))? $psgconf->data_obj('user_info')->find($psgconf->data_obj('sendmail_mta_user')->get())->{'uid'}: 25; $gid = ( defined $psgconf->data_obj('group_info')->find($psgconf->data_obj('sendmail_mta_group')->get()))? $psgconf->data_obj('group_info')->find($psgconf->data_obj('sendmail_mta_group')->get())->{'gid'}: 25; foreach $qdir ($smopts->{'QUEUE_DIR'}, map { $_->{Path} } grep { exists($_->{Path}) } (values %$qgrps)) { if (substr($qdir, -1, 1) ne '*') { $self->_add_queue_dirs($psgconf, $qdir, $uid, $gid); next; } map { $self->_add_queue_dirs( $psgconf, sprintf('%s%0*d', substr($qdir, 0, length($qdir) - 1), length("$num_queues"), $_), $uid, $gid ); } (1 .. $num_queues); } $psgconf->register_actions( PSGConf::Action::MkDir->new( 'name' => $smopts->{'confHOST_STATUS_DIRECTORY'}, 'uid' => $uid, 'gid' => $gid ) ) if (exists($smopts->{'confHOST_STATUS_DIRECTORY'})); $psgconf->register_actions( PSGConf::Action::MkDir->new( 'name' => '/var/spool/clientmqueue', 'uid' => $smmsp_uid, 'gid' => $smmsp_gid, 'mode' => 0770 ), PSGConf::Action::GenerateFile::sendmail_mc->new( 'name' => $psgconf->data_obj('sendmail_cf_dir')->get() . '/sendmail.mc', 'comment_str' => 'dnl', 'description' => 'sendmail configuration file', 'ostype' => $psgconf->data_obj('sendmail_ostype')->get(), 'versionid' => 'sendmail.mc - generated by ' . $0, 'uid' => $uid, 'gid' => $gid, 'defines' => $smopts, 'features' => $smfeatures, 'masquerade_as' => $psgconf->data_obj('sendmail_masquerade_as')->get(), 'exposed_users' => $psgconf->data_obj('sendmail_exposed_users')->get(), 'mailers' => [ qw(local smtp) ], 'queue_groups' => $psgconf->data_obj('sendmail_queue_groups')->get(), 'literal' => $psgconf->data_obj('sendmail_literal')->get() ), PSGConf::Action::GenerateFile::sendmail_cf->new( 'name' => $psgconf->data_obj('sendmail_cf_dir')->get() . '/sendmail.cf', 'comment_str' => undef, 'uid' => $uid, 'gid' => $gid, 'user' => $psgconf->data_obj('sendmail_mta_user')->get(), 'mc_file' => $psgconf->data_obj('sendmail_cf_dir')->get() . '/sendmail.mc', 'm4_command' => $psgconf->data_obj('sendmail_m4_command')->get(), 'sendmail_m4_dir' => $psgconf->data_obj('sendmail_m4_dir')->get() ), PSGConf::Action::GenerateFile::sendmail_mc->new( 'name' => $psgconf->data_obj('sendmail_cf_dir')->get() . '/submit.mc', 'comment_str' => 'dnl', 'description' => 'MSP configuration file', 'versionid' => 'submit.mc - generated by ' . $0, 'uid' => $smmsp_uid, 'gid' => $smmsp_gid, 'defines' => { confCF_VERSION => 'Submit', __OSTYPE__ => '', _USE_DECNET_SYNTAX_ => 1, confTIME_ZONE => 'USE_TZ', confDONT_INIT_GROUPS => 'true', 'confCT_FILE' => '-o ' . $psgconf->data_obj('sendmail_cf_dir')->get() . '/trusted-users', confPID_FILE => $psgconf->data_obj('pidfile_dir')->get() . '/sm-client.pid' }, 'features' => { use_ct_file => undef }, ### the msp feature must come at the end, ### regardless of sort order 'literal' => 'FEATURE(`msp\')dnl' ), PSGConf::Action::GenerateFile::sendmail_cf->new( 'name' => $psgconf->data_obj('sendmail_cf_dir')->get() . '/submit.cf', 'comment_str' => undef, 'uid' => $smmsp_uid, 'gid' => $smmsp_gid, 'mc_file' => $psgconf->data_obj('sendmail_cf_dir')->get() . '/submit.mc', 'user' => $psgconf->data_obj('sendmail_mta_user')->get(), 'm4_command' => $psgconf->data_obj('sendmail_m4_command')->get(), 'sendmail_m4_dir' => $psgconf->data_obj('sendmail_m4_dir')->get() ), PSGConf::Action::GenerateFile::sendmail_trusted_users->new( 'name' => $psgconf->data_obj('sendmail_cf_dir')->get() . '/trusted-users', 'description' => 'trusted users file', 'uid' => $uid, 'gid' => $gid, 'trusted_users' => [ keys %{$psgconf->data_obj('sendmail_trusted_users')->get()} ] ), PSGConf::Action::GenerateFile::sendmail_aliases->new( 'name' => $alias_file, 'description' => 'Local mail aliases', 'uid' => $uid, 'gid' => $gid, 'aliases' => $psgconf->data_obj('sendmail_aliases')->get() ), PSGConf::Action::RestartDaemon->new( name => 'sendmail', pidfile => $psgconf->data_obj('sendmail_options')->find('confPID_FILE'), filename => [ $psgconf->data_obj('sendmail_cf_dir')->get() . '/sendmail.cf', $psgconf->data_obj('sendmail_cf_dir')->get() . '/trusted-users' ] ), PSGConf::Action::RestartDaemon->new( name => 'msp', pidfile => '/var/spool/clientmqueue/sm-client.pid', filename => [ $psgconf->data_obj('sendmail_cf_dir')->get() . '/submit.cf', $psgconf->data_obj('sendmail_cf_dir')->get() . '/trusted-users' ] ), PSGConf::Action::RunCommand->new( name => 'sendmail alias file', command => $psgconf->data_obj('sendmail_newaliases_command')->get() . ' -C ' . $psgconf->data_obj('sendmail_cf_dir')->get() .'/sendmail.cf', check_func => \&_check_map, filename => $alias_file ) ); $access_map = $psgconf->data_obj('sendmail_access_map')->get(); if (%$access_map && exists($smfeatures->{access_db})) { $map_type = $smfeatures->{access_db}; $map_type =~ s/\s.*//; $map_type = $smopts->{DATABASE_MAP_TYPE} if ($map_type eq 'DATABASE_MAP_TYPE'); $map_path = $smfeatures->{access_db}; $map_path = (split(/\s+/, $map_path))[-1]; $psgconf->register_actions( PSGConf::Action::GenerateFile::sendmail_map->new( name => $map_path, description => 'sendmail access map', entries => $access_map ), PSGConf::Action::RunCommand->new( name => 'sendmail access map', command => $psgconf->data_obj('sendmail_makemap_command')->get() . ' ' . $map_type . ' ' . $map_path . ' < ' . $map_path, check_func => \&_check_map, filename => $map_path ) ); } $mailertable = $psgconf->data_obj('sendmail_mailertable')->get(); if (%$mailertable && exists($smfeatures->{mailertable})) { $map_type = $smfeatures->{mailertable}; $map_type =~ s/\s.*//; $map_type = $smopts->{DATABASE_MAP_TYPE} if ($map_type eq 'DATABASE_MAP_TYPE'); $map_path = $smfeatures->{mailertable}; $map_path = (split(/\s+/, $map_path))[-1]; $psgconf->register_actions( PSGConf::Action::GenerateFile::sendmail_map->new( name => $map_path, description => 'sendmail mailertable', 'uid' => $uid, 'gid' => $gid, entries => $mailertable ), PSGConf::Action::RunCommand->new( name => 'sendmail mailertable', command => $psgconf->data_obj('sendmail_makemap_command')->get() . ' ' . $map_type . ' ' . $map_path . ' < ' . $map_path, check_func => \&_check_map, filename => $map_path ) ); } } ############################################################################### ### Constructor ############################################################################### sub new { my ($class, $psgconf) = @_; my ($self, %smopts, $cf_dir); $self = {}; bless($self, $class); $cf_dir = '/etc/mail'; %smopts = ( 'QUEUE_DIR' => '/var/spool/mqueue', 'ALIAS_FILE' => $cf_dir . '/aliases' ); ### So that _add_pkgs knows which directives to look at $self->{name} = 'sendmail'; $self->{enable} = $self->{name} . '_enable'; $self->{packages} = $self->{name} . '_packages'; ### So that _add_syslog knows which directives to look at $self->{syslog} = 'mail'; $self->{facility} = $self->{syslog} . '.info'; $psgconf->register_data( 'sendmail_m4_command' => PSGConf::Data::String->new( 'value_abspath' => 1, value => '/usr/bin/m4' ), 'sendmail_m4_dir' => PSGConf::Data::String->new( 'value_abspath' => 1, 'value' => '/usr/share/sendmail' ), 'sendmail_cf_dir' => PSGConf::Data::String->new( 'value_abspath' => 1, 'value' => $cf_dir ), 'sendmail_path' => PSGConf::Data::String->new( 'value_abspath' => 1, value => '/usr/sbin/sendmail' ), 'sendmail_makemap_command' => PSGConf::Data::String->new( 'value_abspath' => 1, value => '/usr/sbin/makemap' ), 'sendmail_newaliases_command' => PSGConf::Data::String->new( 'value_abspath' => 1, value => '/usr/bin/newaliases' ), 'sendmail_enable' => PSGConf::Data::Boolean->new( 'value' => 'false' ), 'sendmail_packages' => PSGConf::Data::List->new(), 'sendmail_access_map' => PSGConf::Data::Hash->new(), 'sendmail_mailertable' => PSGConf::Data::Hash->new(), 'sendmail_aliases' => PSGConf::Data::Hash->new(), 'sendmail_features' => PSGConf::Data::Hash->new( 'value_optional' => 1 ), 'sendmail_literal' => PSGConf::Data::String->new(), 'sendmail_masquerade_as' => PSGConf::Data::String->new(), 'sendmail_exposed_users' => PSGConf::Data::List->new(), 'sendmail_mta_user' => PSGConf::Data::String->new( value => 'root' ), 'sendmail_mta_group' => PSGConf::Data::String->new( value => 'daemon' ), 'sendmail_msa_user' => PSGConf::Data::String->new( value => 'smmsp' ), 'sendmail_queue_mode' => PSGConf::Data::Integer->new( value => 0755 ), 'sendmail_msa_uid' => PSGConf::Data::Integer->new( value => 25 ), 'sendmail_msa_group' => PSGConf::Data::String->new( value => 'smmsp' ), 'sendmail_msa_gid' => PSGConf::Data::Integer->new( value => 25 ), 'sendmail_ostype' => PSGConf::Data::String->new(), 'sendmail_options' => PSGConf::Data::Hash->new( 'value' => \%smopts ), 'sendmail_trusted_users' => PSGConf::Data::Hash->new( 'value_optional' => 1 ), 'sendmail_queue_groups' => PSGConf::Data::Hash->new( 'value_type' => 'HASH' ), 'sendmail_queue_qf_subdirs' => PSGConf::Data::Boolean->new( 'value' => 'false' ), 'sendmail_num_queues_per_group' => PSGConf::Data::Integer->new( value => 5 ), 'sendmail_use_persistent_queue_runners' => PSGConf::Data::Boolean->new( 'value' => 'false' ) ); $psgconf->register_policy($self, sm_default_pidfile => '_policy_default_pidfile', sm_default_ct_file => '_policy_default_ct_file', sm_default_aliases => '_policy_default_aliases', sm_add_rc_scripts => '_policy_add_rc_scripts', sm_add_tcpwrapper_entry => '_policy_add_tcpwrapper_entry', sm_add_packages => '_add_pkgs', sm_add_syslog => '_add_syslog', sm_add_user => '_policy_add_user' ); return $self; } ############################################################################### ### documentation ############################################################################### 1; __END__ =head1 NAME PSGConf::Control::sendmail - psgconf control class for sendmail =head1 SYNOPSIS In F: Control PSGConf::Control::sendmail =head1 DESCRIPTION The B module provides a B control object for configuring C. It supports the following methods: =over 4 =item new() The constructor. Its parameter is a reference to the B object. It registers the following data objects: =over 4 =item I A B object that contains the absolute path to the C binary. The default is C. =item I A B object that contains the C command to be used to generate map files. The default is C. =item I A B object that contains the C command to be used to generate alias files. The default is C. =item I A B object that indicates whether C should be configured. =item I A B object that lists all packages to install. =item I A B object that contains entries for the F file. =item I A B object that contains entries for the access map. =item I A B object that contains entries for the mailertable. =item I A B object that contains the C command to be used by the B object's check() method. The default is C. =item I A B object that contains the absolute path to the directory containing C's C macros. The default is F. =item I A B object that contains the absolute path to the directory containing C's config files. The default is F. =item I A B object that contains the C macros for the CF file. The hash key is the name of the feature, and the optional value is the argument. If the argument is not enclosed in C<`'> quotes, they will be added. =item I A B object that contains the name of the account to own the sendmail queue directories and run the m4 command to build F files. Defaults to C. =item I A B object that contains the group name to own the sendmail queue directories. Defaults to C. =item I A B object that contains the login for the Sendmail Submission User. Defaults to C. =item I A B object that contains the recommended uid for the B account. Defaults to 25. =item I A B object that contains the group name for the Sendmail Submission User. Defaults to C. =item I A B object that contains the recommended gid for the B account. Defaults to 25. =item I A B object that contains the mode of the queue directories. The default is 0755. =item I A B object that contains literal text to add to the end of the CF file. =item I A B object that contains a hostname for C to masquerade as. =item I A B object that contains list of users to have C expose, even when I is set. =item I A B object that contains the value for the C feature macro for the sendmail mc file. =item I A B object that contains the macros to be Ced in the CF file. By default, C is set to F, and C is set to CF. =item I A B object whose keys are the list of C's trusted users. =item I A B object that contains the queue groups to be configured in the CF file. The hash key is the name of the queue group, and the value is an anonymous hash containing the attributes for that queue group. =item I A B object that indicates whether the C, C, and C queue subdirectories should be created. B If you enable this on a system where C is already running, you must stop C, go to each queue directory and move all C files to the C subdirectory and all C files to the C subdirectory, and then restart C. Failure to do this can result in lost messages! =item I A B object that indicates the number of queues to be created for each queue group. This is only relevant if either I is set or if the value of the C entry in I ends with a C<*>. =item I A B object that indicates whether persistent queue runners should be used. =back The constructor also registers the following policy methods: =over 4 =item I Sets up the C RC script. This is done using the I data object, which are provided by the B module. =item I Adds the C user and group using the I and I data objects, which are provided by the B module. =item I Adds an entry for I to the I object (provided by B). =item I Requests that the C package be installed. This is done using the I object, which is provided by the B module. =item I If the I object does not contain an entry for C, set it to F/sendmail.pid>. (The I object is provided by the B module.) =item I If the I object has been set and the I object does not contain an entry for C, set C to C<-o sendmail_cf_dir/trusted-users> and add an entry for C to the I object. =item I If there is no entry for C in the I data object, set it to C. If there is no entry for C in the I data object, set it to C. If there is no entry for C in the I data object, set it to C. =back =item decide() If I is on, instantiates and registers action objects, as follows: =over 4 =item * Registers B action objects to create any needed queue directories. The queue directories are calculated based on the values of the I and I data objects and the C entry in the I object. =item * Registers a B action object to create the F directory. The directory will be given mode 0770 with the group ID of the C group. (The group ID is obtained from the I object, which is provided by the B module.) =item * Registers B action objects to create the CF and CF files. =item * Registers B action objects to create the CF and CF files. =item * Registers a B object to create the CF file. =item * Registers a B action object to create the F file and a B action object to run C. The path for the file is taken from the C entry in the I object. The C value will be truncated at the first C<,> character. =item * If C is set in the I object, registers a B action object to create the specified directory. =item * If I is set and I contains an entry called C, registers a B object to create the access map and a B object to run C on the new map file. =item * If I is set, registers a B object to create the access map and a B object to run C on the new map file. =back =back =head1 SEE ALSO Csendmail(8)E> Cnewaliases(1)E> Cm4(1)E> Cmakemap(1)E> L L L L L L L L L L L L L L L L L L L =cut