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 126     126   1593 use 5.20.0;
  126         386  
4 126     126   639 use strict;
  126         258  
  126         3095  
5 126     126   577 use warnings;
  126         256  
  126         6347  
6             ##use Mail::Milter::Authentication::Pragmas;
7             # ABSTRACT: Common configuration handling
8             our $VERSION = '3.20230629'; # VERSION
9 126     126   59101 use English;
  126         280160  
  126         754  
10 126     126   163119 use JSON::XS;
  126         704554  
  126         6983  
11 126     126   54189 use TOML;
  126         1229911  
  126         7018  
12 126     126   60615 use Module::Load;
  126         152533  
  126         836  
13 126     126   61329 use Module::Loaded;
  126         89803  
  126         8115  
14              
15 126     126   973 use Exporter qw{ import };
  126         268  
  126         164709  
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 55     55 1 246 my $config = get_config();
90              
91 55         160 my $safe_ident = $IDENT;
92 55         1161 $safe_ident =~ s/[^a-z0-9]/_/g;
93              
94             # Setup some directories
95 55         789 foreach my $type ( qw{ cache lib spool } ) {
96 165         720 my $dir = $config->{$type.'_dir'};
97 165 50       600 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 165 50       1813 if ( $EUID == 0 ) {
106             # We are root, create in global space
107 165         790 $dir = '/var/'.$type.'/authentication_milter';
108 165 100       4838 mkdir $dir if ! -e $dir;
109             # Create the subdir for this IDENT
110 165         1064 $dir .= '/'.$safe_ident;
111 165 100       3056 mkdir $dir if ! -e $dir;
112             # Chown if relevant
113 165         590 my $user = $config->{'runas'};
114 165 50       652 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 165         1518 $config->{$type.'_dir'} = $dir;
131             }
132             }
133              
134              
135             sub set_config {
136 18     18 1 84 my ( $config ) = @_;
137              
138 18         51 my @load_handlers = keys %{ $config->{'handlers'} };
  18         112  
139 18         77 @load_handlers = grep { ! /^\!/ } @load_handlers;
  35         176  
140 18         131 $config->{'load_handlers'} = \@load_handlers;
141              
142 18   50     98 my $protocol = $config->{'protocol'} || 'milter';
143 18         58 $config->{'protocol'} = $protocol;
144 18         84 $CONFIG = $config;
145             }
146              
147              
148             sub load_file {
149 72     72 1 1213 my ( $file ) = @_;
150              
151 72 50       4198 if ( !-e $file ) {
152 0         0 die "Could not find configuration file $file";
153             }
154              
155 72         1414 my $text;
156             {
157 72   50     988 open my $cf, '<',
  72         13166  
158             $file || die "Could not open configuration file $file";
159 72         63337 my @t = <$cf>;
160 72         1767 close $cf;
161 72         3289 $text = join( q{}, @t );
162             }
163              
164 72         1120 my $data;
165              
166 72 50       3432 if ( $file =~ /\.toml$/ ) {
167 0   0     0 $data = TOML::from_toml($text)
168             || die "Error parsing config file $file";
169             }
170             else {
171 72         9048 my $json = JSON::XS->new();
172 72         2213 $json->relaxed(1);
173 72   50     34813 $data = $json->decode($text)
174             || die "Error parsing config file $file";
175             }
176              
177 72         855 return $data;
178             }
179              
180              
181             sub process_config {
182              
183 25306 50   25306 1 68072 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 25306         70128 return $CONFIG;
190             }
191              
192              
193             sub get_config {
194              
195 25306 100   25306 1 155927 if ( $CONFIG ) {
196 25234         63139 return process_config();
197             }
198              
199 72         1863 my $file = $PREFIX . '/authentication_milter';
200 72         1164 my $config;
201 72 50       6748 if ( -e $file . '.toml' ) {
202 0         0 $config = load_file( $file . '.toml' );
203             }
204             else {
205 72         4600 $config = load_file( $file . '.json' );
206             }
207              
208 72         1312 my $folder = $PREFIX . '/authentication_milter.d';
209 72 50       2900 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         1404 my @load_handlers = keys %{ $config->{'handlers'} };
  72         1539  
227 72         797 @load_handlers = grep { ! /^\!/ } @load_handlers;
  882         5235  
228 72         1895 $config->{'load_handlers'} = \@load_handlers;
229              
230 72   100     2719 my $protocol = $config->{'protocol'} || 'milter';
231 72         1597 $config->{'protocol'} = $protocol;
232              
233             # Have we specified an external callback processor?
234 72 50       1115 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 126     126   1024 no strict 'refs'; ## no critic;
  126         258  
  126         18448  
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         857 $CONFIG = $config;
251              
252 72         734 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.20230629
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