Support lmtp protocol
[webmin.git] / dovecot / dovecot-lib.pl
1 # Functions for parsing the dovecot config file
2
3 BEGIN { push(@INC, ".."); };
4 use WebminCore;
5 &init_config();
6
7 @supported_auths = ( "anonymous", "plain", "digest-md5", "cram-md5", "apop" );
8 @mail_envs = ( undef, "maildir:~/Maildir", "mbox:~/mail/:INBOX=/var/mail/%u",
9                "maildir:~/Maildir:mbox:~/mail/" );
10
11 # get_config_file()
12 # Returns the full path to the first valid config file
13 sub get_config_file
14 {
15 foreach my $f (split(/\s+/, $config{'dovecot_config'})) {
16         return $f if (-r $f);
17         }
18 return undef;
19 }
20
21 # get_config()
22 # Returns a list of dovecot config entries
23 sub get_config
24 {
25 if (!scalar(@get_config_cache)) {
26         @get_config_cache = &read_config_file(&get_config_file());
27         }
28 return \@get_config_cache;
29 }
30
31 # read_config_file(filename)
32 # Convert a file into a list od directives
33 sub read_config_file
34 {
35 local ($file) = @_;
36 local $filedir = $file;
37 $filedir =~ s/\/[^\/]+$//;
38 local $lnum = 0;
39 local ($section, @sections);
40 open(CONF, $file);
41 local @lines = <CONF>;
42 close(CONF);
43 local $_;
44 local @rv;
45 local $section;
46 foreach (@lines) {
47         s/\r|\n//g;
48         if (/^\s*(#?)\s*([a-z0-9\_]+)\s*(\S*)\s*\{\s*$/) {
49                 # Start of a section .. add this as a value too
50                 local $oldsection = $section;
51                 if ($section) {
52                         push(@sections, $section);      # save old
53                         }
54                 $section = { 'name' => $2,
55                              'value' => $3,
56                              'enabled' => !$1,
57                              'section' => 1,
58                              'members' => [ ],
59                              'indent' => scalar(@sections),
60                              'line' => $lnum,
61                              'eline' => $lnum,
62                              'file' => $file, };
63                 if ($oldsection) {
64                         $section->{'sectionname'} =
65                                 $oldsection->{'name'};
66                         $section->{'sectionvalue'} =
67                                 $oldsection->{'value'};
68                         }
69                 push(@rv, $section);
70                 }
71         elsif (/^\s*(#?)\s*}\s*$/ && $section) {
72                 # End of a section
73                 $section->{'eline'} = $lnum;
74                 $section->{'eline'} = $lnum;
75                 if (@sections) {
76                         $section = pop(@sections);
77                         }
78                 else {
79                         $section = undef;
80                         }
81                 }
82         elsif (/^\s*(#?)([a-z0-9\_]+)\s+=\s*(.*)/) {
83                 # A directive inside a section
84                 local $dir = { 'name' => $2,
85                                'value' => $3,
86                                'enabled' => !$1,
87                                'line' => $lnum,
88                                'file' => $file, };
89                 if ($section) {
90                         $dir->{'sectionname'} = $section->{'name'};
91                         $dir->{'sectionvalue'} = $section->{'value'};
92                         push(@{$section->{'members'}}, $dir);
93                         $section->{'eline'} = $lnum;
94                         }
95                 push(@rv, $dir);
96                 }
97         elsif (/^\s*!(include|include_try)\s+(\S+)/) {
98                 # Include file(s)
99                 local $glob = $2;
100                 if ($glob !~ /^\//) {
101                         $glob = $filedir."/".$glob;
102                         }
103                 foreach my $i (glob($glob)) {
104                         push(@rv, &read_config_file($i));
105                         }
106                 }
107         $lnum++;
108         }
109 return @rv;
110 }
111
112 # find(name, &config, [disabled-mode], [sectionname], [sectionvalue])
113 # Mode 0=enabled, 1=disabled, 2=both
114 sub find
115 {
116 local ($name, $conf, $mode, $sname, $svalue) = @_;
117 local @rv = grep { !$_->{'section'} &&
118                    $_->{'name'} eq $name &&
119                    ($mode == 0 && $_->{'enabled'} ||
120                     $mode == 1 && !$_->{'enabled'} || $mode == 2) } @$conf;
121 if (defined($sname)) {
122         # If a section was requested, limit to it
123         @rv = grep { $_->{'sectionname'} eq $sname &&
124                      $_->{'sectionvalue'} eq $svalue } @rv;
125         }
126 return wantarray ? @rv : $rv[0];
127 }
128
129 # find_value(name, &config, [disabled-mode], [sectionname], [sectionvalue])
130 # Mode 0=enabled, 1=disabled, 2=both
131 sub find_value
132 {
133 local @rv = &find(@_);
134 if (wantarray) {
135         return map { $_->{'value'} } @rv;
136         }
137 else {
138         return $rv[0]->{'value'};
139         }
140 }
141
142 # find_section(name, &config, [disabled-mode], [sectionname], [sectionvalue])
143 # Returns a Dovecot config section object
144 sub find_section
145 {
146 local ($name, $conf, $mode, $sname, $svalue) = @_;
147 local @rv = grep { $_->{'section'} &&
148                    $_->{'name'} eq $name &&
149                    ($mode == 0 && $_->{'enabled'} ||
150                     $mode == 1 && !$_->{'enabled'} || $mode == 2) } @$conf;
151 if (defined($sname)) {
152         # If a section was requested, limit to it
153         @rv = grep { $_->{'sectionname'} eq $sname &&
154                      $_->{'sectionvalue'} eq $svalue } @rv;
155         }
156 return wantarray ? @rv : $rv[0];
157 }
158
159 # save_directive(&conf, name|&dir, value, [sectionname], [sectionvalue])
160 # Updates one directive in the config file
161 sub save_directive
162 {
163 local ($conf, $name, $value, $sname, $svalue) = @_;
164 local $dir = ref($name) ? $name : &find($name, $conf, 0, $sname, $svalue);
165 local $newline = ref($name) ? "$name->{'name'} = $value" : "$name = $value";
166 if ($sname) {
167         $newline = "  ".$newline;
168         }
169 if ($dir && defined($value)) {
170         # Updating some directive
171         local $lref = &read_file_lines($dir->{'file'});
172         $lref->[$dir->{'line'}] = $newline;
173         $dir->{'value'} = $value;
174         }
175 elsif ($dir && !defined($value)) {
176         # Deleting some directive
177         local $lref = &read_file_lines($dir->{'file'});
178         splice(@$lref, $dir->{'line'}, 1);
179         &renumber($conf, $dir->{'line'}, $dir->{'file'}, -1);
180         @$conf = grep { $_ ne $dir } @$conf;
181         }
182 elsif (!$dir && defined($value)) {
183         # Adding some directive .. put it after the commented version, if any
184         local $cmt = &find($name, $conf, 1, $sname, $svalue);
185         if ($cmt) {
186                 # After comment
187                 local $lref = &read_file_lines($cmt->{'file'});
188                 splice(@$lref, $cmt->{'line'}+1, 0, $newline);
189                 &renumber($conf, $cmt->{'line'}+1, $cmt->{'file'}, 1);
190                 push(@$conf, { 'name' => $name,
191                                'value' => $value,
192                                'line' => $cmt->{'line'}+1,
193                                'sectionname' => $sname,
194                                'sectionvalue' => $svalue });
195                 }
196         elsif ($sname) {
197                 # Put at end of section
198                 local @insect = grep { $_->{'sectionname'} eq $sname &&
199                                        $_->{'sectionvalue'} eq $svalue } @$conf;
200                 @insect || &error("Failed to find section $sname $svalue !");
201                 local $lref = &read_file_lines($insect[$#insect]->{'file'});
202                 local $line = $insect[$#insect]->{'line'}+1;
203                 splice(@$lref, $line, 0, $newline);
204                 &renumber($conf, $line, $insect[$#insect]->{'file'}, 1);
205                 push(@$conf, { 'name' => $name,
206                                'value' => $value,
207                                'line' => $line,
208                                'sectionname' => $sname,
209                                'sectionvalue' => $svalue });
210                 }
211         else {
212                 # Need to put at end of main config
213                 local $lref = &read_file_lines(&get_config_file());
214                 push(@$lref, $newline);
215                 push(@$conf, { 'name' => $name,
216                                'value' => $value,
217                                'line' => scalar(@$lref)-1,
218                                'sectionname' => $sname,
219                                'sectionvalue' => $svalue });
220                 }
221         }
222 }
223
224 # save_section(&conf, &section)
225 # Updates one section in the config file
226 sub save_section
227 {
228 local ($conf, $section) = @_;
229 local $lref = &read_file_lines($section->{'file'});
230 local $indent = "  " x $section->{'indent'};
231 local @newlines;
232 push(@newlines, $indent.$section->{'name'}." ".$section->{'value'}." {");
233 foreach my $m (@{$section->{'members'}}) {
234         push(@newlines, $indent."  ".$m->{'name'}." = ".$m->{'value'});
235         }
236 push(@newlines, $indent."}");
237 local $oldlen = $section->{'eline'} - $section->{'line'} + 1;
238 splice(@$lref, $section->{'line'}, $oldlen, @newlines);
239 &renumber($conf, $section->{'eline'}, $section->{'file'},
240           scalar(@newlines)-$oldlen);
241 $section->{'eline'} = $section->{'line'} + scalar(@newlines) - 1;
242 }
243
244 # renumber(&conf, line, file, offset)
245 sub renumber
246 {
247 local ($conf, $line, $file, $offset) = @_;
248 foreach my $c (@$conf) {
249         if ($c->{'file'} eq $file) {
250                 $c->{'line'} += $offset if ($c->{'line'} >= $line);
251                 $c->{'eline'} += $offset if ($c->{'eline'} >= $line);
252                 }
253         }
254 }
255
256 # is_dovecot_running()
257 # Returns the PID if the server process is active, undef if not
258 sub is_dovecot_running
259 {
260 # Try the configured PID file first
261 local $pid =&check_pid_file($config{'pid_file'});
262 return $pid if ($pid);
263
264 # Look in the base dir
265 local $base = &find_value("base_dir", &get_config(), 2);
266 return &check_pid_file("$base/master.pid");
267 }
268
269 # get_initscript()
270 # Returns the full path to the Dovecot init script
271 sub get_initscript
272 {
273 if ($config{'init_script'}) {
274         &foreign_require("init", "init-lib.pl");
275         if ($init::init_mode eq "init") {
276                 return &init::action_filename($config{'init_script'});
277                 }
278         }
279 return undef;
280 }
281
282 # stop_dovecot()
283 # Attempts to stop the dovecot server process, returning an error message or
284 # undef if successful
285 sub stop_dovecot
286 {
287 local $script = &get_initscript();
288 if ($script) {
289         local $out = &backquote_logged("$script stop 2>&1 </dev/null");
290         return $? ? "<pre>$out</pre>" : undef;
291         }
292 else {
293         local $pid = &is_dovecot_running();
294         if ($pid && kill('TERM', $pid)) {
295                 return undef;
296                 }
297         else {
298                 return $text{'stop_erunning'};
299                 }
300         }
301 }
302
303 # start_dovecot()
304 # Attempts to start the dovecot server process, returning an error message or
305 # undef if successful
306 sub start_dovecot
307 {
308 local $script = &get_initscript();
309 local $cmd = $script ? "$script start" : $config{'dovecot'};
310 local $temp = &transname();
311 &system_logged("$cmd >$temp 2>&1 </dev/null &");
312 sleep(1);
313 local $out = &read_file_contents($temp);
314 &unlink_file($temp);
315 return &is_dovecot_running() ? undef : "<pre>$out</pre>";
316 }
317
318 # apply_configration()
319 # Stop and re-start the Dovecot server
320 sub apply_configuration
321 {
322 local $pid = &is_dovecot_running();
323 if ($pid) {
324         &stop_dovecot();
325         local $err;
326         for(my $i=0; $i<5; $i++) {
327                 $err = &start_dovecot();
328                 last if (!$err);
329                 sleep(1);
330                 }
331         return $err;
332         }
333 else {
334         return $text{'stop_erunning'};
335         }
336 }
337
338 # getdef(name, [&mapping])
339 # Returns 'Default (value)' for some config
340 sub getdef
341 {
342 local $def = &find_value($_[0], &get_config(), 1);
343 if (defined($def)) {
344         local $map;
345         if ($_[1]) {
346                 ($map) = grep { $_->[0] eq $def } @{$_[1]};
347                 }
348         if (defined($map)) {
349                 return "$text{'default'} ($map->[1])";
350                 }
351         elsif ($def) {
352                 return "$text{'default'} ($def)";
353                 }
354         }
355 return $text{'default'};
356 }
357
358 # get_dovecot_version()
359 # Returns the dovecot version number, or undef if not available
360 sub get_dovecot_version
361 {
362 local $out = `$config{'dovecot'} --version 2>&1`;
363 return $out =~ /([0-9\.]+)/ ? $1 : undef;
364 }
365
366 sub list_lock_methods
367 {
368 local ($forindex) = @_;
369 return ( "dotlock", "fcntl", "flock", $forindex ? ( ) : ( "lockf" ) );
370 }
371
372 # lock_dovecot_files([&conf])
373 # Lock all files in the Dovecot config
374 sub lock_dovecot_files
375 {
376 local ($conf) = @_;
377 $conf ||= &get_config();
378 foreach my $f (&unique(map { $_->{'file'} } @$conf)) {
379         &lock_file($f);
380         }
381 }
382
383 # unlock_dovecot_files([&conf])
384 # Release lock on all files
385 sub unlock_dovecot_files
386 {
387 local ($conf) = @_;
388 $conf ||= &get_config();
389 foreach my $f (reverse(&unique(map { $_->{'file'} } @$conf))) {
390         &unlock_file($f);
391         }
392 }
393
394 # get_supported_protocols()
395 # Returns the list of usable protocols for the current Dovecot version
396 sub get_supported_protocols
397 {
398 if (&get_dovecot_version() >= 2) {
399         return ( "imap", "pop3", "lmtp" );
400         }
401 else {
402         return ( "imap", "pop3", "imaps", "pop3s" );
403         }
404 }
405
406 1;
407 r
408