File Coverage

blib/lib/Mail/Milter/Authentication/Config.pm
Criterion Covered Total %
statement 86 126 68.2
branch 15 40 37.5
condition 5 10 50.0
subroutine 15 16 93.7
pod 6 6 100.0
total 127 198 64.1


line stmt bran cond sub pod time code
1             package Mail::Milter::Authentication::Config;
2             # ABSTRACT: Load config files for Authentication Milter
3 127     127   2056 use 5.20.0;
  127         389  
4 127     127   641 use strict;
  127         201  
  127         2516  
5 127     127   518 use warnings;
  127         252  
  127         6436  
6             ##use Mail::Milter::Authentication::Pragmas;
7             # ABSTRACT: Common configuration handling
8             our $VERSION = '3.20230911'; # VERSION
9 127     127   62184 use English;
  127         279019  
  127         753  
10 127     127   163234 use JSON::XS;
  127         732891  
  127         9794  
11 127     127   55952 use TOML;
  127         1227209  
  127         7227  
12 127     127   64753 use Module::Load;
  127         152914  
  127         846  
13 127     127   59781 use Module::Loaded;
  127         84633  
  127         7694  
14              
15 127     127   946 use Exporter qw{ import };
  127         317  
  127         162038  
16             our @EXPORT_OK = qw{
17             get_config
18             set_config
19             default_config
20             setup_config
21             };
22              
23              
24             our $PREFIX = '/etc';
25             our $IDENT = 'authentication_milter';
26             my $CONFIG;
27              
28              
29             sub default_config {
30 0     0 1 0 my $config = {
31             'debug' => 0,
32             'dryrun' => 0,
33             'logtoerr' => 0,
34             'error_log' => '/var/log/authentication_milter.err',
35             'extended_log' => 1,
36             'legacy_log' => 0,
37             'connection' => 'inet:12345@localhost',
38             'umask' => '0000',
39             'runas' => 'nobody',
40             'rungroup' => 'nogroup',
41             'listen_backlog' => 20,
42             'check_for_dequeue' => 60,
43             'min_children' => 20,
44             'max_children' => 200,
45             'min_spare_children' => 10,
46             'max_spare_children' => 20,
47             'max_requests_per_child' => 200,
48             'protocol' => 'milter',
49             'connect_timeout' => 30,
50             'command_timeout' => 30,
51             'content_timeout' => 300,
52             'dequeue_timeout' => 300,
53             'addheader_timeout' => 30,
54             'dns_timeout' => 10,
55             'dns_retry' => 2,
56             'tempfail_on_error' => '1',
57             'tempfail_on_error_authenticated' => '0',
58             'tempfail_on_error_local' => '0',
59             'tempfail_on_error_trusted' => '0',
60             'milter_quarantine' => '0',
61             'ip_map' => {},
62             'authserv_id' => '',
63             'handlers' => {},
64             'cache_dir' => '/var/cache/authentication_milter',
65             'spool_dir' => '/var/spool/authentication_milter',
66             'lib_dir' => '/var/lib/authentication_milter',
67             'lock_file' => '',
68             };
69              
70 0         0 require Mail::Milter::Authentication;
71 0         0 my $installed_handlers = Mail::Milter::Authentication::get_installed_handlers();
72 0         0 foreach my $handler ( @$installed_handlers ) {
73 0         0 my $handler_module = 'Mail::Milter::Authentication::Handler::' . $handler;
74 0         0 load $handler_module;
75 0 0       0 if ( $handler_module->can( 'default_config' ) ) {
76 0         0 $config->{'handlers'}->{ $handler } = $handler_module->default_config();
77             }
78             else {
79 0         0 $config->{'handlers'}->{ $handler } = {};
80             }
81             }
82              
83 0         0 return $config;
84              
85             }
86              
87              
88             sub setup_config {
89 58     58 1 355 my $config = get_config();
90              
91 58         247 my $safe_ident = $IDENT;
92 58         1642 $safe_ident =~ s/[^a-z0-9]/_/g;
93              
94             # Setup some directories
95 58         777 foreach my $type ( qw{ cache lib spool } ) {
96 174         649 my $dir = $config->{$type.'_dir'};
97 174 50       711 if ( $dir ) {
98             # Value supplied, MUST already be setup
99             # Check that we can use the given directory
100 0 0       0 die $type.'_dir does not exist' if ! -e $dir;
101 0 0       0 die $type.'_dir is not a directory' if ! -d $dir;
102 0 0       0 die $type.'_dir is not a writable' if ! -w $dir;
103             }
104             else {
105 174 50       1856 if ( $EUID == 0 ) {
106             # We are root, create in global space
107 174         806 $dir = '/var/'.$type.'/authentication_milter';
108 174 100       64034 mkdir $dir if ! -e $dir;
109             # Create the subdir for this IDENT
110 174         1025 $dir .= '/'.$safe_ident;
111 174 100       3379 mkdir $dir if ! -e $dir;
112             # Chown if relevant
113 174         851 my $user = $config->{'runas'};
114 174 50       622 if ($user) {
115 0         0 my ($login,$pass,$uid,$gid) = getpwnam($user);
116 0         0 chown $uid, $gid, $dir;
117             }
118             }
119             else {
120             # We are a user! Create something in a temporary space
121 0         0 $dir = join( '_',
122             '/tmp/authentication_milter',
123             $type,
124             $EUID,
125             $safe_ident,
126             );
127 0 0       0 mkdir $dir if ! -e $dir;
128             }
129             }
130 174         1784 $config->{$type.'_dir'} = $dir;
131             }
132             }
133              
134              
135             sub set_config {
136 21     21 1 84 my ( $config ) = @_;
137              
138 21         54 my @load_handlers = keys %{ $config->{'handlers'} };
  21         123  
139 21         72 @load_handlers = grep { ! /^\!/ } @load_handlers;
  44         198  
140 21         116 $config->{'load_handlers'} = \@load_handlers;
141              
142 21   50     97 my $protocol = $config->{'protocol'} || 'milter';
143 21         63 $config->{'protocol'} = $protocol;
144 21         95 $CONFIG = $config;
145             }
146              
147              
148             sub load_file {
149 72     72 1 1905 my ( $file ) = @_;
150              
151 72 50       4309 if ( !-e $file ) {
152 0         0 die "Could not find configuration file $file";
153             }
154              
155 72         1185 my $text;
156             {
157 72   50     1381 open my $cf, '<',
  72         13278  
158             $file || die "Could not open configuration file $file";
159 72         52187 my @t = <$cf>;
160 72         2221 close $cf;
161 72         3655 $text = join( q{}, @t );
162             }
163              
164 72         1172 my $data;
165              
166 72 50       2762 if ( $file =~ /\.toml$/ ) {
167 0   0     0 $data = TOML::from_toml($text)
168             || die "Error parsing config file $file";
169             }
170             else {
171 72         6783 my $json = JSON::XS->new();
172 72         1501 $json->relaxed(1);
173 72   50     34289 $data = $json->decode($text)
174             || die "Error parsing config file $file";
175             }
176              
177 72         768 return $data;
178             }
179              
180              
181             sub process_config {
182              
183 27217 50   27217 1 73417 if ( exists( $CONFIG->{ '_external_callback_processor' } ) ) {
184 0 0       0 if ( $CONFIG->{ '_external_callback_processor' }->can( 'process_config' ) ) {
185 0         0 $CONFIG->{ '_external_callback_processor' }->process_config( $CONFIG );
186             }
187             }
188              
189 27217         71328 return $CONFIG;
190             }
191              
192              
193             sub get_config {
194              
195 27217 100   27217 1 168914 if ( $CONFIG ) {
196 27145         61537 return process_config();
197             }
198              
199 72         2007 my $file = $PREFIX . '/authentication_milter';
200 72         868 my $config;
201 72 50       7319 if ( -e $file . '.toml' ) {
202 0         0 $config = load_file( $file . '.toml' );
203             }
204             else {
205 72         4676 $config = load_file( $file . '.json' );
206             }
207              
208 72         934 my $folder = $PREFIX . '/authentication_milter.d';
209 72 50       2798 if ( -d $folder ) {
210 0         0 my $dh;
211 0         0 opendir $dh, $folder;
212             my @config_files =
213             sort
214 0         0 grep { $_ =~ /\.(json|toml)$/ }
215 0         0 grep { not $_ =~ /^\./ }
  0         0  
216             readdir($dh);
217 0         0 closedir $dh;
218 0         0 foreach my $file ( @config_files ) {
219 0         0 $file =~ /(^.*)\.(json|toml)$/;
220 0         0 my $handler = $1;
221             ## ToDo Consider what to do if config already exists in .json config
222 0         0 $config->{'handlers'}->{$handler} = load_file( join( '/', $folder, $file ) );
223             }
224             }
225              
226 72         900 my @load_handlers = keys %{ $config->{'handlers'} };
  72         2271  
227 72         1538 @load_handlers = grep { ! /^\!/ } @load_handlers;
  882         5203  
228 72         1786 $config->{'load_handlers'} = \@load_handlers;
229              
230 72   100     2473 my $protocol = $config->{'protocol'} || 'milter';
231 72         1363 $config->{'protocol'} = $protocol;
232              
233             # Have we specified an external callback processor?
234 72 50       1314 if ( exists( $config->{ 'external_callback_processor' } ) ) {
235             # Try and load the handler
236 0         0 my $handler = $config->{ 'external_callback_processor' };
237 0 0       0 if ( ! is_loaded ( $handler ) ) {
238 0         0 eval {
239 127     127   1138 no strict 'refs'; ## no critic;
  127         259  
  127         23044  
240 0         0 load $handler;
241 0         0 $config->{ '_external_callback_processor' } = $handler->new();
242             };
243 0 0       0 if ( my $error = $@ ) {
244 0         0 delete $config->{ 'external_callback_processor' };
245 0         0 warn "Error loading external callback processor module: $error";
246             }
247             }
248             }
249              
250 72         1136 $CONFIG = $config;
251              
252 72         2048 return process_config();
253              
254             }
255              
256             1;
257              
258             __END__
259              
260             =pod
261              
262             =encoding UTF-8
263              
264             =head1 NAME
265              
266             Mail::Milter::Authentication::Config - Load config files for Authentication Milter
267              
268             =head1 VERSION
269              
270             version 3.20230911
271              
272             =head1 SYNOPSIS
273              
274             Load in the configuration data, does some processing on handlers loaded before returning
275             config to the caller.
276              
277             If the $Mail::Milter::Authentication::Config::PREFIX variable is set then the config file
278             will be read from the supplied directory rather than /etc/
279              
280             =head1 DESCRIPTION
281              
282             Load in configuration data.
283              
284             =head1 FUNCTIONS
285              
286             =head2 I<default_config()>
287              
288             Return a default configuration including defaults from handler modules.
289              
290             This is not the default config used by the system if no config is present, rather it is the config
291             which is presented to the user as an example default config when using the help feature.
292              
293             =head2 I<setup_config()>
294              
295             Called during startup, setup some config options.
296              
297             =head2 I<set_config( $config )>
298              
299             Set the config hashref, primarily used for testing.
300              
301             =head2 I<load_file( $file )>
302              
303             Internal function used to load the config from /etc/authentication_milter.json
304              
305             =head2 I<process_config()>
306              
307             Process the loaded config with the callback if required.
308              
309             This is the name of a Module to load, the process_config method of the instantiated object
310             will be called with $config as the argument.g
311              
312             package ConfigProcessor;
313              
314             sub new {
315             ...
316             }
317              
318             sub process_config {
319             my ( $self, $config ) = @_;
320             }
321              
322             1;
323              
324             =head2 I<get_config()>
325              
326             Return the config hashref, load from file(s) if required.
327              
328             =head1 AUTHOR
329              
330             Marc Bradshaw <marc@marcbradshaw.net>
331              
332             =head1 COPYRIGHT AND LICENSE
333              
334             This software is copyright (c) 2020 by Marc Bradshaw.
335              
336             This is free software; you can redistribute it and/or modify it under
337             the same terms as the Perl 5 programming language system itself.
338              
339             =cut