2 # Common functions for parsing and editing the spamassassin config file
4 BEGIN { push(@INC, ".."); };
9 $warn_procmail = $config{'warn_procmail'};
10 if ($module_info{'usermin'}) {
11 # Running under Usermin, editing user's personal config file
12 &switch_to_remote_user();
13 &create_user_config_dirs();
14 if ($config{'local_cf'} !~ /^\//) {
15 # Path is relative to home dir
16 &set_config_file("$remote_user_info[7]/$config{'local_cf'}");
17 if ($local_cf =~ /^(.*)\// && !-d $1) {
22 &set_config_file($config{'local_cf'});
24 $database_userpref_name = $remote_user;
25 $include_config_files = !$config{'mode'} || $config{'readfiles'};
27 $max_awl_keys = $userconfig{'max_awl'} || 200;
30 # Running under Webmin, typically editing global config file
31 %access = &get_module_acl();
32 if ($access{'file'}) {
33 &set_config_file($access{'file'});
36 &set_config_file($config{'local_cf'});
38 if ($access{'nocheck'}) {
41 $database_userpref_name = $config{'dbglobal'} || '@GLOBAL';
42 $include_config_files = 1;
43 $add_to_db = $config{'addto'};
44 $max_awl_keys = $config{'max_awl'} || 200;
46 $ldap_spamassassin_attr = $config{'attr'} || 'spamassassin';
47 $ldap_username_attr = $config{'uid'} || 'uid';
49 # set_config_file(file)
50 # Change the default file read by get_config. Under Webmin, checks if this file
51 # is accessible to the current user
55 if (!$module_info{'usermin'}) {
56 # Check for valid file
58 $cans{$access{'file'}} = 1 if ($access{'file'});
59 foreach my $f (split(/\s+/, $access{'files'})) {
63 $cans{$file} || &error(&text('index_ecannot',
64 "<tt>".&html_escape($file)."</tt>"));
68 $add_cf = !-d $local_cf ? $local_cf :
69 $module_info{'usermin'} ? "$local_cf/user_prefs" :
73 sub set_config_file_in
76 $header_subtext = undef;
79 if (!$module_info{'usermin'} && $in{'file'}) {
80 &set_config_file($in{'file'});
81 $header_subtext = $in{'title'} || "<tt>$in{'file'}</tt>";
82 $redirect_url = "index.cgi?file=".&urlize($in{'file'}).
83 "&title=".&urlize($in{'title'});
84 $form_hiddens = &ui_hidden("file", $in{'file'}).
85 &ui_hidden("title", $in{'title'});
86 $module_index_link = $redirect_url;
90 # get_config([file], [for-global])
91 # Return a structure containing the contents of the spamassassin config file
94 local $forglobal = $_[1];
96 if ($include_config_files || $forglobal) {
97 # Reading from file(s)
99 local $file = $_[0] || $local_cf;
101 # A directory of files - read them all
103 local @files = sort { $a cmp $b } readdir(DIR);
106 foreach $f (@files) {
107 if ($f =~ /\.(cf|pre)$/) {
108 local $add = &get_config("$file/$f",$forglobal);
109 map { $_->{'index'} += scalar(@rv) } @$add;
115 # A single file that can be read right here
120 if (/^(\S+)\s*(.*)$/) {
121 local $dir = { 'name' => $1,
123 'index' => scalar(@rv),
128 [ split(/\s+/, $dir->{'value'}) ];
137 if ($config{'mode'} == 1 || $config{'mode'} == 2) {
138 # Add from SQL database
139 local $dbh = &connect_spamassasin_db();
140 &error($dbh) if (!ref($dbh));
141 local $cmd = $dbh->prepare("select preference,value from userpref where username = ?");
142 $cmd->execute(!$forglobal ? $database_userpref_name :
143 $config{'dbglobal'} ? $config{'dbglobal'} : '@GLOBAL');
144 while(my ($name, $value) = $cmd->fetchrow()) {
145 local $dir = { 'name' => $name,
147 'index' => scalar(@rv),
148 'mode' => $config{'mode'} };
150 [ split(/\s+/, $dir->{'value'}) ];
155 elsif ($config{'mode'} == 3 && !$forglobal) {
157 local $ldap = &connect_spamassassin_ldap();
158 &error($ldap) if (!ref($ldap));
159 local $uinfo = &get_ldap_user($ldap);
162 foreach my $a ($uinfo->get_value($ldap_spamassassin_attr)) {
163 local ($name, $value) = split(/\s+/, $a, 2);
164 local $dir = { 'name' => $name,
166 'index' => scalar(@rv),
167 'aindex' => $aindex++,
169 'mode' => $config{'mode'} };
171 [ split(/\s+/, $dir->{'value'}) ];
180 # find(name, &config)
184 foreach $c (@{$_[1]}) {
185 push(@rv, $c) if (lc($c->{'name'}) eq lc($_[0]));
187 return wantarray ? @rv : $rv[0];
190 # find_value(name, &config)
193 local @rv = map { $_->{'value'} } &find(@_);
194 return wantarray ? @rv : $rv[0];
197 # save_directives(&config, name|&old, &new, valuesonly)
198 # Update the config file with some directives
201 if ($module_info{'usermin'} && $local_cf =~ /^(.*)\/([^\/]+)$/) {
202 # Under Usermin, make sure .spamassassin exists
205 &make_dir($spamdir, 0755);
208 local @old = ref($_[1]) ? @{$_[1]} : &find($_[1], $_[0]);
209 local @new = $_[3] ? &make_directives($_[1], $_[2]) : @{$_[2]};
211 for($i=0; $i<@old || $i<@new; $i++) {
214 $line = $new[$i]->{'name'};
215 $line .= " ".$new[$i]->{'value'} if ($new[$i]->{'value'} ne '');
217 if ($old[$i] && $new[$i]) {
218 # Replacing a directive
219 if ($old[$i]->{'name'} eq $new[$i]->{'name'} &&
220 $old[$i]->{'value'} eq $new[$i]->{'value'}) {
224 if ($old[$i]->{'mode'} == 0) {
226 local $lref = &read_file_lines($old[$i]->{'file'});
227 $lref->[$old[$i]->{'line'}] = $line;
229 elsif ($old[$i]->{'mode'} == 1 || $old[$i]->{'mode'} == 2) {
231 local $dbh = &connect_spamassasin_db();
232 &error($dbh) if (!ref($dbh));
233 local $cmd = $dbh->prepare("update userpref set value = ? where username = ? and preference = ? and value = ?");
234 $cmd->execute($new[$i]->{'value'},
235 $database_userpref_name,
237 $old[$i]->{'value'});
240 elsif ($old[$i]->{'mode'} == 3) {
241 # In LDAP - modify the attribute
242 local $ldap = &connect_spamassassin_ldap();
243 &error($ldap) if (!ref($ldap));
244 local $uinfo = &get_ldap_user($ldap);
245 $uinfo || &error(&text('ldap_euser',
246 $database_userpref_name));
247 local @values = $uinfo->get_value(
248 $ldap_spamassassin_attr);
249 $values[$old[$i]->{'aindex'}] = $new[$i]->{'name'}." ".
251 local $rv = $ldap->modify(
253 replace => { $ldap_spamassassin_attr =>
255 if (!$rv || $rv->code) {
256 &error(&text('eldap',
257 $rv ? $rv->error : "Unknown modify error"));
260 $_[0]->[$old[$i]->{'index'}] = $new[$i];
263 # Deleting a directive
264 if ($old[$i]->{'mode'} == 0) {
266 local $lref = &read_file_lines($old[$i]->{'file'});
267 splice(@$lref, $old[$i]->{'line'}, 1);
268 foreach $c (@{$_[0]}) {
269 if ($c->{'line'} > $old[$i]->{'line'} &&
270 $c->{'file'} eq $old[$i]->{'file'}) {
275 elsif ($old[$i]->{'mode'} == 1 || $old[$i]->{'mode'} == 2) {
277 local $dbh = &connect_spamassasin_db();
278 &error($dbh) if (!ref($dbh));
279 local $cmd = $dbh->prepare("delete from userpref where username = ? and preference = ? and value = ?");
280 $cmd->execute($database_userpref_name,
282 $old[$i]->{'value'});
285 elsif ($old[$i]->{'mode'} == 3) {
286 # From LDAP .. get current values, and remove this one
287 local $ldap = &connect_spamassassin_ldap();
288 &error($ldap) if (!ref($ldap));
289 local $uinfo = &get_ldap_user($ldap);
290 $uinfo || &error(&text('ldap_euser',
291 $database_userpref_name));
292 local @values = $uinfo->get_value(
293 $ldap_spamassassin_attr);
294 splice(@values, $old[$i]->{'aindex'}, 1);
295 local $rv = $ldap->modify(
297 replace => { $ldap_spamassassin_attr =>
299 if (!$rv || $rv->code) {
300 &error(&text('eldap',
301 $rv ? $rv->error : "Unknown delete error"));
306 splice(@{$_[0]}, $old[$i]->{'index'}, 1);
307 foreach $c (@{$_[0]}) {
308 if ($c->{'index'} > $old[$i]->{'index'}) {
315 local $addmode = scalar(@old) ? $old[0]->{'mode'} :
316 $new[$i]->{'name'} =~ /^user_scores_/ ? 0 :
317 $add_to_db ? $config{'mode'} : 0;
320 local $lref = &read_file_lines($add_cf);
321 $new[$i]->{'line'} = @$lref;
324 elsif ($addmode == 1 || $addmode == 2) {
326 local $dbh = &connect_spamassasin_db();
327 &error($dbh) if (!ref($dbh));
328 local $cmd = $dbh->prepare("insert into userpref (username, preference, value) values (?, ?, ?)");
329 $cmd->execute($database_userpref_name,
331 $new[$i]->{'value'});
334 elsif ($addmode == 3) {
336 local $ldap = &connect_spamassassin_ldap();
337 &error($ldap) if (!ref($ldap));
338 local $uinfo = &get_ldap_user($ldap);
339 $uinfo || &error(&text('ldap_euser',
340 $database_userpref_name));
341 local $rv = $ldap->modify(
343 add => { $ldap_spamassassin_attr =>
344 $new[$i]->{'name'}." ".$new[$i]->{'value'} });
345 if (!$rv || $rv->code) {
346 &error(&text('eldap',
347 $rv ? $rv->error : "Unknown add error"));
350 $new[$i]->{'mode'} = $addmode;
351 $new[$i]->{'index'} = @{$_[0]};
352 push(@{$_[0]}, $new[$i]);
357 # make_directives(name, &values)
360 return map { { 'name' => $_[0],
361 'value' => $_ } } @{$_[1]};
366 # edit_table(name, &headings, &&values, &sizes, [&convfunc], blankrows)
367 # Display a table of values for editing, with one blank row
371 local $rv = &ui_columns_start($_[1]);
373 local $cfunc = $_[4] || \&default_convfunc;
374 local $blanks = $_[5] || 1;
375 foreach $v (@{$_[2]}, map { [ ] } (1 .. $blanks)) {
377 for($j=0; $j<@{$_[1]}; $j++) {
378 push(@cols, &$cfunc($j, "$_[0]_${i}_${j}", $_[3]->[$j],
381 $rv .= &ui_columns_row(\@cols);
384 $rv .= &ui_columns_end();
388 # default_convfunc(column, name, size, value)
391 return "<input name=$_[1] size=$_[2] value='".&html_escape($_[3])."'>";
394 # parse_table(name, &parser)
395 # Parse the inputs from a table and return an array of results
399 local $pfunc = $_[1] || \&default_parsefunc;
400 for($i=0; defined($in{"$_[0]_${i}_0"}); $i++) {
401 local ($j, $v, @vals);
402 for($j=0; defined($v = $in{"$_[0]_${i}_${j}"}); $j++) {
405 local $p = &$pfunc("$_[0]_${i}", @vals);
406 push(@rv, $p) if (defined($p));
411 # default_parsefunc(rowname, value, ...)
412 # Returns a value or undef if empty, or calls &error if invalid
413 sub default_parsefunc
415 return $_[1] ? join(" ", @_[1..$#_]) : undef;
418 # start_form(cgi, header, [right-header])
421 local ($cgi, $header, $right) = @_;
422 print &ui_form_start($cgi, "post");
423 print &ui_table_start($header, "width=100%", 2, undef, $right);
427 # end_form(buttonname, buttonvalue, ...)
430 print &ui_table_end();
432 for(my $i=0; $i<@_; $i+=2 ) {
433 local $al = $i == 0 ? "align=left" :
434 $i == @_-2 ? "align=right" : "align=center";
435 push(@buts, [ $_[$i], $_[$i+1] ]);
437 print &ui_form_end(\@buts);
440 # yes_no_field(name, value, default)
443 local $v = !$_[1] ? -1 : $_[1]->{'value'};
444 local $def = &find_default($_[0], $_[2]) ? $text{'yes'} : $text{'no'};
445 return &ui_radio($_[0], $v,
446 [ [ 1, $text{'yes'} ], [ 0, $text{'no'} ],
447 [ -1, $text{'default'}." (".$def.")" ] ]);
450 # parse_yes_no(&config, name)
453 &save_directives($_[0], $_[1], $in{$_[1]} == 1 ? [ 1 ] :
454 $in{$_[1]} == 0 ? [ 0 ] : [ ], 1);
457 # option_field(name, value, default, &opts)
460 local $v = !$_[1] ? -1 : $_[1]->{'value'};
461 local $def = &find_default($_[0], $_[2]);
462 local ($defopt) = grep { $_->[0] eq $def } @{$_[3]};
463 return &ui_radio($_[0], $v,
464 [ @{$_[3]}, [ -1, "$text{'default'} ($defopt->[1])" ] ]);
469 &save_directives($_[0], $_[1], $in{$_[1]} == -1 ? [ ] : [ $in{$_[1]} ], 1);
472 # opt_field(name, value, size, default)
475 local $def = &find_default($_[0], $_[3]) if ($_[3]);
476 return &ui_opt_textbox($_[0],
477 !$_[1] ? undef : ref($_[1]) ? $_[1]->{'value'} : $_[1],
478 $_[2], $text{'default'}.($_[3] ? " ($def)" : ""));
481 # parse_opt(&config, name, [&checkfunc])
484 if (defined($in{"$_[1]_default"}) && $in{"$_[1]_default"} eq $in{$_[1]} ||
485 !defined($in{"$_[1]_default"}) && $in{"$_[1]_def"}) {
486 &save_directives($_[0], $_[1], [ ], 1);
489 &{$_[2]}($in{$_[1]}) if ($_[2]);
490 &save_directives($_[0], $_[1], [ $in{$_[1]} ], 1);
494 # edit_textbox(name, &values, width, height, [disabled])
497 return &ui_textarea($_[0], join("\n", @{$_[1]}), $_[3], $_[2], undef, $_[4]);
500 # parse_textbox(&config, name)
503 $in{$_[1]} =~ s/^\s+//;
504 $in{$_[1]} =~ s/\s+$//;
505 local @v = split(/\s+/, $in{$_[1]});
506 &save_directives($_[0], $_[1], \@v, 1);
510 # Returns the full paths to the procmail config files in use, the last one
511 # being the user's config
514 if ($module_info{'usermin'}) {
516 push(@rv, $config{'global_procmailrc'});
517 push(@rv, $config{'procmailrc'} || $procmail::procmailrc);
521 return ( $access{'procmailrc'} || $config{'procmailrc'} || $procmail::procmailrc );
525 # find_default(name, compiled-in-default)
528 if ($config{'global_cf'}) {
529 if (!defined($global_config_cache)) {
530 $global_config_cache = &get_config($config{'global_cf'}, 1);
532 local $v = &find_value($_[0], $global_config_cache);
533 return $v if (defined($v));
539 # Returns 1 if some page can be used, 0 if not
543 if ($module_info{'usermin'}) {
544 %avail_icons = map { $_, 1 } split(/,/, $config{'avail_icons'});
547 %avail_icons = map { $_, 1 } split(/,/, $access{'avail'});
549 local $p = $_[0] eq "simple" ? "header" : $_[0];
550 return $avail_icons{$p};
553 # can_use_check(page)
554 # Calls error if some page cannot be used
557 &can_use_page($_[0]) || &error($text{'ecannot'});
560 # get_spamassassin_version(&out)
561 sub get_spamassassin_version
564 &execute_command("$config{'spamassassin'} -V", undef, \$out, \$out, 0, 1);
565 ${$_[0]} = $out if ($_[0]);
566 return $out =~ /(version|Version:)\s+(\S+)/ ? $2 : undef;
569 # version_atleast(num)
572 if (!$version_cache) {
573 $version_cache = &get_spamassassin_version();
575 return $version_cache >= $_[0];
581 &foreign_require("mailbox", "mailbox-lib.pl");
582 local ($sf) = grep { $_->{'spam'} } &mailbox::list_folders();
586 # disable_indexing(&folder)
589 if (!$config{'index_spam'}) {
590 $mailbox::config{'index_min'} = 1000000000;
591 unlink(&mailbox::user_index_file($_[0]->{'file'}));
596 # Returns the PIDs and names of SpamAssassin daemon processes like spamd
600 foreach $pn (split(/\s+/, $config{'processes'})) {
601 push(@pids, map { [ $_, $pn ] } &find_byname($pn));
608 local $conf = &get_config();
609 @spam_files = &unique(map { $_->{'file'} } @$conf);
611 foreach $f (@spam_files) {
616 sub unlock_spam_files
619 foreach $f (@spam_files) {
624 # show_buttons(number)
627 print "<table width=100%> <tr>\n";
628 local $onclick = "onClick='return check_clicks(form)'"
629 if (defined(&check_clicks_function));
630 print "<td align=left><input type=submit name=inbox value=\"$text{'mail_inbox'}\" $onclick></td>\n";
631 print "<td align=left><input type=submit name=whitelist value=\"$text{'mail_whitelist2'}\" $onclick></td>\n";
632 if (&has_command($config{'sa_learn'})) {
633 print "<td align=center><input type=submit name=ham value=\"$text{'mail_ham'}\" $onclick></td>\n";
635 print "<td align=right><input type=submit name=delete value=\"$text{'mail_delete'}\" $onclick></td>\n";
636 print "<td align=right><input type=submit name=razor value=\"$text{'mail_razor'}\" $onclick></td>\n";
637 print "</tr></table>\n";
641 # Re-start all SpamAssassin processes, or return an error message
644 if ($config{'restart_cmd'}) {
645 local $out = &backquote_logged(
646 "$config{'restart_cmd'} 2>&1 </dev/null");
647 if ($? || $out =~ /error|failed/i) {
648 return "<pre>$out</pre>";
652 local @pids = &get_process_pids();
653 @pids || return $text{'apply_none'};
656 &kill_logged("HUP", $p->[0]);
662 # find_spam_recipe(&recipes)
663 # Returns the recipe that runs spamassassin
667 foreach $r (@{$_[0]}) {
668 if ($r->{'action'} =~ /spamassassin/i ||
669 $r->{'action'} =~ /spamc/i) {
676 # find_file_recipe(&recipes)
677 # returns the recipe for delivering mail based on the x-spam-status header
681 foreach $r (@{$_[0]}) {
682 foreach $c (@{$r->{'conds'}}) {
683 if ($c->[1] =~ /x-spam-status/i) {
691 # find_delete_recipe(&recipes)
692 # returns the recipe for delete mail based on the x-spam-level header, and
693 # the level it deletes at.
694 sub find_delete_recipe
697 foreach $r (grep { $_->{'action'} eq '/dev/null' } @{$_[0]}) {
698 foreach $c (@{$r->{'conds'}}) {
699 if ($c->[1] =~ /x-spam-level:\s+((\\\*)+)/i) {
700 return ($r, length($1)/2);
707 # find_virtualmin_recipe(&recipes)
708 # Returns the recipe that runs the Virtualmin lookup command
709 sub find_virtualmin_recipe
712 foreach $r (@{$_[0]}) {
713 if ($r->{'action'} =~ /^VIRTUALMIN=/) {
720 # find_force_default_receipe(&recipes)
721 # Returns the recipe that forces delivery to $DEFAULT, used by Virtualmin and
722 # others to prevent per-user .procmailrc settings
723 sub find_force_default_receipe
726 foreach $r (@{$_[0]}) {
727 if ($r->{'action'} eq '$DEFAULT' && !@{$r->{'conds'}}) {
734 # get_simple_tests(&conf)
738 local (@simple, %simple);
739 foreach my $h (&find("header", $conf)) {
740 if ($h->{'value'} =~ /^(\S+)\s+(\S+)\s+=~\s+\/(.*)\/(\S*)\s*$/) {
741 push(@simples, { 'header_dir' => $h,
746 $simples{$1} = $simples[$#simples];
749 foreach my $b (&find("body", $conf), &find("full", $conf),
750 &find("uri", $conf)) {
751 if ($b->{'value'} =~ /^(\S+)\s+\/(.*)\/(\S*)\s*$/) {
752 push(@simples, { $b->{'name'}.'_dir' => $b,
754 'header' => $b->{'name'},
757 $simples{$1} = $simples[$#simples];
760 foreach my $s (&find("score", $conf)) {
761 if ($s->{'value'} =~ /^(\S+)\s+(\S+)/ && $simples{$1}) {
762 $simples{$1}->{'score_dir'} = $s;
763 $simples{$1}->{'score'} = $2;
766 foreach my $d (&find("describe", $conf)) {
767 if ($d->{'value'} =~ /^(\S+)\s+(\S.*)/ && $simples{$1}) {
768 $simples{$1}->{'describe_dir'} = $d;
769 $simples{$1}->{'describe'} = $2;
775 # get_procmail_command()
776 # Returns the command that should be used in /etc/procmailrc to call
777 # spamassassin, such as spamc or the full spamassassin path
778 sub get_procmail_command
780 if ($config{'procmail_cmd'} eq '*') {
782 if (&get_process_pids()) {
783 local $spamc = &has_command("spamc");
784 return $spamc if ($spamc);
786 return &has_command($config{'spamassassin'});
788 elsif ($config{'procmail_cmd'}) {
789 return $config{'procmail_cmd'};
792 return &has_command($config{'spamassassin'});
796 # execute_before(section)
797 # If a before-change command is configured, run it. If it fails, call error
800 local ($section) = @_;
801 if ($config{'before_cmd'}) {
802 $ENV{'SPAM_SECTION'} = $section;
804 local $rv = &execute_command(
805 $config{'before_cmd'}, undef, \$out, \$out);
806 $rv && &error(&text('before_ecmd',
807 "<pre>".&html_escape($out)."</pre>"));
811 # execute_after(section)
812 # If a after-change command is configured, run it. If it fails, call error
815 local ($section) = @_;
816 if ($config{'after_cmd'}) {
817 $ENV{'SPAM_SECTION'} = $section;
819 local $rv = &execute_command(
820 $config{'after_cmd'}, undef, \$out, \$out);
821 $rv && &error(&text('after_ecmd',
822 "<pre>".&html_escape($out)."</pre>"));
826 # check_spamassassin_db()
827 # Checks if the LDAP or MySQL backend can be contacted, and if not returns
829 sub check_spamassassin_db
831 if ($config{'mode'} == 0) {
832 return undef; # Local files always work
834 elsif ($config{'mode'} == 1 || $config{'mode'} == 2) {
835 # Connect to a database
836 local $dbh = &connect_spamassasin_db();
837 return $dbh if (!ref($dbh));
838 local $testcmd = $dbh->prepare("select * from userpref limit 1");
839 if (!$testcmd || !$testcmd->execute()) {
840 undef($connect_spamassasin_db_cache);
842 return &text('connect_equery', "<tt>$config{'db'}</tt>",
843 "<tt>userpref</tt>");
846 undef($connect_spamassasin_db_cache);
850 elsif ($config{'mode'} == 3) {
852 local $ldap = &connect_spamassassin_ldap();
853 return $ldap if (!ref($ldap));
854 local $rv = $ldap->search(base => $config{'base'},
855 filter => "(uid=$remote_user)",
857 if (!$rv || $rv->code) {
858 return &text('connect_ebase', "<tt>$config{'base'}</tt>",
859 $rv ? $rv->error : "Unknown search error");
864 return "Unknown config mode $config{'mode'} !";
868 # connect_spamassasin_db()
869 # Attempts to connect to the SpamAssasin MySQL or PostgreSQL database. Returns
870 # a driver handle on success, or an error message string on failure.
871 sub connect_spamassasin_db
873 if (defined($connect_spamassasin_db_cache)) {
874 return $connect_spamassasin_db_cache;
876 local $driver = $config{'mode'} == 1 ? "mysql" : "Pg";
880 \$drh = DBI->install_driver(\$driver);
883 return &text('connect_edriver', "<tt>DBD::$driver</tt>");
885 local $dbistr = &make_dbistr($driver, $config{'db'}, $config{'server'});
886 local $dbh = $drh->connect($dbistr,
887 $config{'user'}, $config{'pass'}, { });
888 $dbh || return &text('connect_elogin',
889 "<tt>$config{'db'}</tt>", $drh->errstr)."\n";
890 $connect_spamassasin_db_cache = $dbh;
894 # connect_spamassassin_ldap()
895 # Attempts to connect to the configured LDAP DB, and returns the handle on
896 # success, or an error message on failure.
897 sub connect_spamassassin_ldap
899 if (defined($connect_spamassasin_ldap_cache)) {
900 return $connect_spamassasin_ldap_cache;
902 eval "use Net::LDAP";
904 return &text('connect_eldapmod', "<tt>Net::LDAP</tt>");
906 local $port = $config{'port'} || 389;
907 local $inet6 = !&to_ipaddress($config{'server'}) &&
908 &to_ip6address($config{'server'});
909 local $ldap = Net::LDAP->new($config{'server'},
913 return &text('connect_eldap', "<tt>$config{'server'}</tt>", $port);
915 local $mesg = $ldap->bind(dn => $config{'user'}, password => $config{'pass'});
916 if (!$mesg || $mesg->code) {
917 return &text('connect_eldaplogin', "<tt>$config{'server'}</tt>",
918 "<tt>$config{'user'}</tt>",
919 $mesg ? $mesg->error : "Unknown error");
921 $connect_spamassasin_ldap_cache = $ldap;
927 local ($driver, $db, $host) = @_;
929 if ($driver eq "mysql") {
930 $rv = "database=$db";
932 elsif ($driver eq "Pg") {
939 $rv .= ";host=$host";
944 # get_ldap_user(&ldap, [username])
945 # Returns the LDAP object for a user, or undef if not found
948 local ($ldap, $user) = @_;
949 $user ||= $database_userpref_name;
950 #if (exists($get_ldap_user_cache{$user})) {
951 # return $get_ldap_user_cache{$user};
953 local $rv = $ldap->search(base => $config{'base'},
954 filter => "($ldap_username_attr=$user)",
956 if (!$rv || $rv->code) {
957 &error(&text('eldap', $rv ? $rv->error : "Search failed"));
959 local ($uinfo) = $rv->all_entries;
960 $get_ldap_user_cache{$user} = $uinfo;
964 # get_auto_whitelist_file([user])
965 # Returns the base path to the auto whitelist DBM, if any.
966 sub get_auto_whitelist_file
969 local $conf = &get_config();
970 local $awp = &find("auto_whitelist_path", $conf);
972 $awp = &find_default("auto_whitelist_path");
974 $awp ||= "~/.spamassassin/auto-whitelist";
977 local @uinfo = $module_info{'usermin'} ? @remote_user_info :
978 $user ? getpwnam($user) : ( );
979 return undef if (scalar(@uinfo) == 0);
980 $awp =~ s/^(\~|\$HOME)\//$uinfo[7]\//;
982 $awp = "$uinfo[7]/$awp";
987 local @real = glob("$awp.*");
988 $awp = undef if (!@real);
993 # open_auto_whitelist_dbm([user])
994 # Ties the %awl hash to the autowhitelist DBM file. Returns 1 if successful, or
995 # 0 if it could not be opened, or -1 if empty.
996 sub open_auto_whitelist_dbm
999 local $awp = &get_auto_whitelist_file($user);
1000 return 0 if (!$awp);
1002 foreach my $cls ('DB_File', 'GDBM_File', 'SDBM_File') {
1006 tie(%awl, $cls, $awp, O_RDWR, 0755) || next;
1007 if (scalar(keys %awl)) {
1012 return $anyok ? -1 : 0;
1015 # close_auto_whitelist_dbm()
1016 # Disconnects the global %awl hash from the DBM file, flushing changes to disk
1017 sub close_auto_whitelist_dbm
1022 # supports_auto_whitelist()
1023 # Returns 1 if SpamAssassin is doing auto-whitelisting for the current user,
1024 # 2 if for multiple users.
1025 sub supports_auto_whitelist
1027 if ($module_info{'usermin'}) {
1028 return &get_auto_whitelist_file() ? 1 : 0;
1038 return 1 if ($module_info{'usermin'}); # Only one user anyway
1039 if ($access{'awl_users'}) {
1040 # Check if on user list
1041 return &indexof($user, split(/\s+/, $access{'awl_users'})) >= 0;
1043 elsif ($access{'awl_groups'}) {
1044 # Check if the user is a member of any of the allowed groups
1046 local @uinfo = getpwnam($user);
1047 return 0 if (!scalar(@uinfo));
1048 local @ginfo = getgrgid($uinfo[3]);
1049 $ugroups{$ginfo[0]}++ if (scalar(@ginfo));
1050 foreach my $o (&other_groups($user)) {
1053 local @can = grep { $ugroups{$_} } split(/\s+/, $access{'awl_groups'});
1054 return @can ? 1 : 0;
1062 # list_spamassassin_languages()
1063 # Returns a list of language codes and descriptions
1064 sub list_spamassassin_languages
1067 open(LANGS, "$module_root_directory/langs");
1069 if (/^(\S+)\s+(.*)/) {
1070 push(@rv, [ $1, $2 ]);
1077 # list_spamassassin_locales()
1078 # Returns a list of locale codes and descriptions
1079 sub list_spamassassin_locales
1082 open(LANGS, "$module_root_directory/locales");
1084 if (/^(\S+)\s+(.*)/) {
1085 push(@rv, [ $1, $2 ]);
1092 # list_spamassassin_plugins()
1093 # Returns a list of plugins enabled, both globally and for this user
1094 sub list_spamassassin_plugins
1097 if ($config{'global_cf'}) {
1098 my $gconf = &get_config($config{'global_cf'}, 1);
1099 push(@rv, &find_value("loadplugin", $gconf));
1101 my $conf = &get_config();
1102 push(@rv, &find_value("loadplugin", $conf));