This commit is contained in:
Gregory Colpart 2015-08-21 01:28:17 +02:00
commit f570931b5b
46 changed files with 3100 additions and 0 deletions

11
.depend Normal file
View File

@ -0,0 +1,11 @@
policy.cmo: policy.cmi
policy.cmx: policy.cmi
rules.cmo: spf.cmi policy.cmi rules.cmi
rules.cmx: spf.cmx policy.cmx rules.cmi
server.cmo: server.cmi
server.cmx: server.cmi
spf.cmo: spf.cmi
spf.cmx: spf.cmi
whitelister.cmo: server.cmi rules.cmi policy.cmi
whitelister.cmx: server.cmx rules.cmx policy.cmx
rules.cmi: policy.cmi

1
.pc/.quilt_patches Normal file
View File

@ -0,0 +1 @@
debian/patches

1
.pc/.quilt_series Normal file
View File

@ -0,0 +1 @@
series

1
.pc/.version Normal file
View File

@ -0,0 +1 @@
2

2
.pc/applied-patches Normal file
View File

@ -0,0 +1,2 @@
reject_unknown_client.patch
disable_spf.patch

View File

@ -0,0 +1,95 @@
##########################################################################
# #
# whitelister : a Whitelister Policy Daemon for Postfix #
# ~~~~~~~~~~~ #
# #
# Copyright (C) 2005 AAEGE.org #
# Author : Pierre Habouzit <pierre.habouzit@m4x.org> #
# ____________________________________________________________________ #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; either version 2 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the Free Software #
# Foundation, Inc., #
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
##########################################################################
VERSION = 0.8
PROGRAM = whitelister
PKG_DIST = $(PROGRAM)-$(VERSION)
REPO = $(shell svn info | grep URL | sed -e 's,URL *: *,,')
PKGS = -package syslog
CFLAGS = -O2 -Wall -fPIC
CCLIB = -cclib -lspf
CCOPT = $(foreach opt,$(CFLAGS),-ccopt $(opt))
OCAMLC = ocamlfind ocamlc $(PKGS) $(CCOPT) $(CCLIB)
OCAMLOPT= ocamlfind ocamlopt $(PKGS) $(CCOPT) $(CCLIB)
OCAMLDEP= ocamlfind ocamldep $(PKGS)
BIB = str.cmxa unix.cmxa syslog.cmxa
CMX = spf.cmx policy.cmx rules.cmx server.cmx whitelister.cmx
COB = spfstubs.o
##############################################################
all: $(PROGRAM)
whitelister: $(COB) $(CMX)
$(OCAMLOPT) -o $@ $(BIB) $^
headers: Makefile *.ml *.mli
headache -h tpl/header $^
dist:
@rm -rf $(PKG_DIST) $(PKG_DIST).tar.gz
@svn export $(REPO) $(PKG_DIST)
@rm -rf $(PKG_DIST)/debian
@tar czf $(PKG_DIST).tar.gz $(PKG_DIST)
@rm -rf $(PKG_DIST)
@echo -e "\ndistribution built in $(PKG_DIST).tar.gz\n"
##############################################################
.SUFFIXES: .mli .ml .cmi .cmo .cmx .mll .mly .c .o
.c.o:
$(OCAMLC) $(CCOPT) -c $<
.mli.cmi:
$(OCAMLC) -c $<
.ml.cmo:
$(OCAMLC) -c $<
.ml.cmx:
$(OCAMLOPT) -c $<
.mll.ml:
$(OCAMLLEX) $<
##############################################################
cleanbyte:
rm -rf *.{cm?,o} *~
clean: cleanbyte
rm -f .depend
rm -f $(PROGRAM)
.depend depend: *.ml *.mli
rm -f .depend
$(OCAMLDEP) *.ml *.mli > .depend
include .depend

View File

@ -0,0 +1,117 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
exception ParseError
exception Unknown
exception DSN
type t = (string, string) Hashtbl.t
(* Private : access to hashtbl *)
let get h k =
try Hashtbl.find h k
with Not_found -> raise Unknown
let getu h k =
try Hashtbl.find h k
with Not_found -> "unknown"
let check_policy pcy =
if getu pcy "request" = "smtpd_access_policy" then
pcy
else
raise ParseError
let domain s =
try
Str.string_after s ((String.index s '@')+1)
with Not_found -> raise Unknown (* case of rpcts / sender that are not email addresses *)
(* Private : log formats *)
let log_end pcy =
Printf.sprintf
"from=<%s> to=<%s> proto=%s helo=<%s>"
( getu pcy "sender" )
( getu pcy "recipient" )
( getu pcy "protocol_name" )
( getu pcy "helo_name" )
let log_start : t -> string = fun pcy ->
Printf.sprintf
"%s from %s[%s][%s]"
( getu pcy "protocol_state" )
( getu pcy "client_name" )
( getu pcy "client_address" )
( getu pcy "reverse_client_name" )
(* public *)
let read ic =
let res = Hashtbl.create 13 in
try
while true do
try
let line = input_line ic in
if String.length line = 0 then raise End_of_file;
let i = String.index line '=' in
Hashtbl.add res (Str.string_before line i) (Str.string_after line (i+1))
with Not_found -> raise ParseError
done;
assert false
with
| End_of_file -> check_policy res
let clear = Hashtbl.clear
let client_address h = get h "client_address"
let client_name h = get h "client_name"
let reverse_client_name h = get h "reverse_client_name"
let helo_name h = get h "helo_name"
let sender h =
try
(
match Hashtbl.find h "sender" with
| "" -> raise DSN
| s -> s
)
with Not_found -> raise DSN
let rcpt_domain h = domain (get h "recipient")
let sender_domain h = domain (sender h)
let log_format level answer pcy =
Printf.sprintf "%s: %s: %s; %s"
level (log_start pcy) answer (log_end pcy)
let spf_explain pcy =
let sender = getu pcy "sender" in
let ip = getu pcy "client_address" in
Printf.sprintf " - Please see http://spf.pobox.com/why.html?sender=%s&ip=%s" sender ip

View File

@ -0,0 +1,45 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
type t
exception ParseError
exception Unknown
exception DSN
val read : in_channel -> t
val clear : t -> unit
val client_address : t -> string
val client_name : t -> string
val reverse_client_name : t -> string
val sender : t -> string
val helo_name : t -> string
val rcpt_domain: t -> string
val sender_domain : t -> string
val log_format : string -> string -> t -> string
val spf_explain : t -> string

View File

@ -0,0 +1,116 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
open Policy
exception Dirty of string
exception Reject of string
type rhbl_type = Helo | Rcpt | Sender | Client
(* HELPERS *)
(** [rev_ip "ip1.ip2.ip3.ip4"] returns ["ip4.ip3.ip2.ip1"] *)
let rev_ip ip =
let l = Str.split (Str.regexp_string ".") ip in
String.concat "." (List.rev l)
(** [dns_check r i h] returns :
* [i+1] if ["$r.$h"] domain exists,
* [i] if domain does not exists,
* [i+1] if any error occur.
* This is meant to be use with List.fold_left
*)
let dns_check rad host =
try
let _ = Unix.gethostbyname (rad ^ "." ^ host) in
raise (Dirty ("blacklisted by " ^ host))
with
| Not_found -> ()
| Dirty _ as e -> raise e
| _ -> raise (Dirty "rbl failure")
let rhbl_extract_domain =
function
| Helo -> helo_name
| Rcpt -> helo_name
| Sender -> sender_domain
| Client -> client_name
(* PUBLIC INTERFACE *)
let check_rbl rbl_list pcy =
try
let revip = rev_ip (client_address pcy) in
List.iter (dns_check revip) rbl_list
with
| Policy.Unknown -> raise (Dirty "no `client_address' found")
| Policy.DSN -> ()
let check_rhbl kind rhbl_list pcy =
try
let host = (rhbl_extract_domain kind) pcy in
List.iter (dns_check host) rhbl_list
with
| Policy.Unknown ->
(
match kind with
| Helo | Rcpt -> raise (Dirty "no `helo_name' found")
| Sender -> raise (Dirty "no `sender_name' found")
| Client -> raise (Dirty "no `client_name' found")
)
| Policy.DSN -> ()
open Spf
type spf_mode = Spf_off | Spf_normal | Spf_strict | Spf_paranoid
let check_spf mode dorej pcy =
if mode != Spf_off then
let fail s =
if dorej then raise (Reject (s ^ Policy.spf_explain pcy)) else raise (Dirty s)
in try
match spf_query (sender pcy) (client_address pcy) (helo_name pcy) with
| SPF_pass -> ()
| SPF_softerr -> fail "SPF soft error"
| SPF_harderr -> fail "SPF hard error"
| SPF_none -> if mode != Spf_normal then raise (Dirty "no SPF record found")
| SPF_neutral -> if mode = Spf_paranoid then raise (Dirty "SPF neutral")
with
| Spf.Error -> raise (Dirty "SPF Internal error")
| Policy.DSN -> ()
let check_dns_client dorej pcy =
if dorej then
if (client_name pcy) = "unknown" then raise (Dirty "no client_name (reject_unknown_client)")
else ()
let check_dns_rev_client dorej pcy =
if dorej then
if (reverse_client_name pcy) = "unknown" then raise (Dirty "no reverse_client_name (reject_unknown_reverse_client)")
else ()

View File

@ -0,0 +1,41 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
exception Error
type spf_result =
SPF_pass | SPF_neutral | SPF_none | SPF_softerr | SPF_harderr
external _spf_query : string -> string -> string -> int = "spf_query"
let spf_query host ip helo =
match _spf_query host ip helo with
| 0 -> SPF_pass
| 1 -> SPF_neutral
| 2 -> SPF_none
| 3 -> SPF_softerr
| 4 -> SPF_harderr
| _ -> raise Error

View File

@ -0,0 +1,33 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
exception Error
type spf_result =
SPF_pass | SPF_neutral | SPF_none | SPF_softerr | SPF_harderr
val spf_query : string -> string -> string -> spf_result

View File

@ -0,0 +1,42 @@
#include <caml/mlvalues.h>
#include <caml/memory.h>
#include <spf.h>
value spf_query(value from, value ip, value helo)
{
int res;
peer_info_t* peer_info;
CAMLparam3(from, ip, helo);
peer_info = SPF_init("whitelister", String_val(ip), NULL, NULL, NULL, 0, 0);
SPF_smtp_helo(peer_info, String_val(helo));
SPF_smtp_from(peer_info, String_val(from));
res = peer_info->RES = SPF_policy_main(peer_info);
SPF_close(peer_info);
switch(res)
{
case SPF_PASS:
CAMLreturn(Val_int(0));
case SPF_NEUTRAL:
CAMLreturn(Val_int(1));
case SPF_NONE:
CAMLreturn(Val_int(2));
case SPF_S_FAIL:
CAMLreturn(Val_int(3));
case SPF_H_FAIL:
CAMLreturn(Val_int(4));
default: /* SPF_ERROR, SPF_UNKNOWN, SPF_UNMECH */
CAMLreturn(Val_int(5));
}
}

View File

@ -0,0 +1,184 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
(* types *)
type config = {
mutable pidf: string;
mutable sock: Unix.sockaddr;
mutable user: string;
mutable group: string;
mutable verb: int;
mutable spf: Rules.spf_mode;
mutable spfrej: bool;
mutable rbl: string list;
mutable rhbl_client: string list;
mutable rhbl_rcpt: string list;
mutable rhbl_sender: string list;
mutable rhbl_helo: string list;
mutable dns_client: bool;
mutable dns_rev_client: bool;
}
(* Checker *)
let log msg =
let lg = Syslog.openlog ~facility:`LOG_MAIL ~flags:[`LOG_PID] "whitelister" in
Syslog.syslog lg `LOG_INFO msg;
Syslog.closelog lg
let log_event level answer pcy =
log (Policy.log_format level answer pcy)
let checker cfg s =
let oc = Unix.out_channel_of_descr s in
try
while true do
let pcy = Policy.read (Unix.in_channel_of_descr s) in
let ans = (
try
Rules.check_rbl cfg.rbl pcy;
Rules.check_rhbl Rules.Helo cfg.rhbl_helo pcy;
Rules.check_rhbl Rules.Sender cfg.rhbl_sender pcy;
Rules.check_rhbl Rules.Rcpt cfg.rhbl_rcpt pcy;
Rules.check_rhbl Rules.Client cfg.rhbl_client pcy;
Rules.check_spf cfg.spf cfg.spfrej pcy;
Rules.check_dns_client cfg.dns_client pcy;
Rules.check_dns_rev_client cfg.dns_rev_client pcy;
if cfg.verb > 0 then log_event "Clean" "OK" pcy;
"OK"
with
| Rules.Dirty s ->
log_event "Dirty" (Printf.sprintf "DUNNO (%s)" s) pcy;
"DUNNO"
| Rules.Reject s ->
let s' = "REJECT "^s in
log_event "Reject" s' pcy;
s'
) in
Printf.fprintf oc "action=%s\n\n" ans;
flush oc;
Policy.clear pcy (* not needed, but can help the GC *)
done
with _ -> Unix.shutdown s Unix.SHUTDOWN_ALL
(* Configuration *)
let empty_config () = {
pidf = "/var/run/whitelister.pid" ;
sock = Unix.ADDR_INET (Unix.inet_addr_of_string "127.0.0.1", 10000) ;
user = "nobody" ;
group = "nogroup" ;
verb = 0 ;
spf = Rules.Spf_off ;
spfrej = false;
rbl = [] ;
rhbl_client = [] ;
rhbl_rcpt = [] ;
rhbl_sender = [] ;
rhbl_helo = [] ;
dns_client = false;
dns_rev_client = false;
}
let to_bool s =
function
| "0" | "off" | "no" -> false
| "1" | "yes" | "on" -> true
| _ -> prerr_endline ("cannot read a boolean value for `"^s^"', possible values are on/off, yes/no, 1/0"); exit 1
let update_config cfg file =
let ic = open_in file in
try
while true do
let line = input_line ic in
if String.length line > 0 && line.[0] <> '#' then
match Str.split (Str.regexp "[ \t:]+") line with
| [] -> ()
| ["pidf"; f] -> cfg.pidf <- f
| ["sock"; s] -> cfg.sock <- Unix.ADDR_UNIX s
| ["sock"; ip; p] -> cfg.sock <- Unix.ADDR_INET (Unix.inet_addr_of_string ip, int_of_string p)
| ["user"; u] -> cfg.user <- u
| ["group"; g] -> cfg.group <- g
| ["verb"; "0"] -> cfg.verb <- 0
| ["verb"; "1"] -> cfg.verb <- 1
| ["spf"; "0"] -> cfg.spf <- Rules.Spf_off
| ["spf"; "1"] -> cfg.spf <- Rules.Spf_normal
| ["spf"; "2"] -> cfg.spf <- Rules.Spf_strict
| ["spf"; "3"] -> cfg.spf <- Rules.Spf_paranoid
| ["spfrej"; b] -> cfg.spfrej <- to_bool "spfrej" b
| ["rbl"; h] -> cfg.rbl <- h::cfg.rbl
| ["rhbl_client"; h] -> cfg.rhbl_client <- h::cfg.rhbl_client
| ["rhbl_helo"; h] -> cfg.rhbl_helo <- h::cfg.rhbl_helo
| ["rhbl_rcpt"; h] -> cfg.rhbl_rcpt <- h::cfg.rhbl_rcpt
| ["rhbl_sender"; h] -> cfg.rhbl_sender <- h::cfg.rhbl_sender
| ["dns_client"; d] -> cfg.dns_client <- to_bool "dns_client" d
| ["dns_rev_client"; e] -> cfg.dns_rev_client <- to_bool "dns_rev_client" e
(* deprecated settings *)
| ["rhbl"; h] -> prerr_endline "rhbl is deprecated, it defaults to rhbl_client which may not be what you want.";
cfg.rhbl_client <- h::cfg.rhbl_client
| _ -> prerr_string (Printf.sprintf "`%s' is not a valid config line\n" line)
done;
assert false
with End_of_file -> close_in ic; cfg
let read_config () =
let found_files = List.filter Sys.file_exists ["/etc/whitelister.conf" ; "whitelister.conf"] in
List.fold_left update_config (empty_config ()) found_files
(* Server thingies *)
open Server
let my_server_loop cfg sock =
pidfile_write ();
pidfile_close ();
log ("started, listening to " ^ (string_of_sockaddr cfg.sock));
while true do
let s = fst (unix_do Unix.accept sock) in
double_fork (fun s -> Unix.close sock; checker cfg s ; exit 0) s;
Unix.close s
done
(* Main LOOP *)
let _ =
let cfg = read_config () in
pidfile_open cfg.pidf;
pidfile_write ();
daemonize (cfg.user,cfg.group) (my_server_loop cfg) (bind_to_sock cfg.sock)

View File

@ -0,0 +1,95 @@
##########################################################################
# #
# whitelister : a Whitelister Policy Daemon for Postfix #
# ~~~~~~~~~~~ #
# #
# Copyright (C) 2005 AAEGE.org #
# Author : Pierre Habouzit <pierre.habouzit@m4x.org> #
# ____________________________________________________________________ #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; either version 2 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the Free Software #
# Foundation, Inc., #
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
##########################################################################
VERSION = 0.8
PROGRAM = whitelister
PKG_DIST = $(PROGRAM)-$(VERSION)
REPO = $(shell svn info | grep URL | sed -e 's,URL *: *,,')
PKGS = -package syslog
CFLAGS = -O2 -Wall -fPIC
CCLIB = -cclib -lspf
CCOPT = $(foreach opt,$(CFLAGS),-ccopt $(opt))
OCAMLC = ocamlfind ocamlc $(PKGS) $(CCOPT) $(CCLIB)
OCAMLOPT= ocamlfind ocamlopt $(PKGS) $(CCOPT) $(CCLIB)
OCAMLDEP= ocamlfind ocamldep $(PKGS)
BIB = str.cmxa unix.cmxa syslog.cmxa
CMX = spf.cmx policy.cmx rules.cmx server.cmx whitelister.cmx
COB = spfstubs.o
##############################################################
all: $(PROGRAM)
whitelister: $(COB) $(CMX)
$(OCAMLOPT) -o $@ $(BIB) $^
headers: Makefile *.ml *.mli
headache -h tpl/header $^
dist:
@rm -rf $(PKG_DIST) $(PKG_DIST).tar.gz
@svn export $(REPO) $(PKG_DIST)
@rm -rf $(PKG_DIST)/debian
@tar czf $(PKG_DIST).tar.gz $(PKG_DIST)
@rm -rf $(PKG_DIST)
@echo -e "\ndistribution built in $(PKG_DIST).tar.gz\n"
##############################################################
.SUFFIXES: .mli .ml .cmi .cmo .cmx .mll .mly .c .o
.c.o:
$(OCAMLC) $(CCOPT) -c $<
.mli.cmi:
$(OCAMLC) -c $<
.ml.cmo:
$(OCAMLC) -c $<
.ml.cmx:
$(OCAMLOPT) -c $<
.mll.ml:
$(OCAMLLEX) $<
##############################################################
cleanbyte:
rm -rf *.{cm?,o} *~
clean: cleanbyte
rm -f $(PROGRAM)
.depend depend: *.ml *.mli
rm -f .depend
$(OCAMLDEP) *.ml *.mli > .depend
include .depend

View File

@ -0,0 +1,115 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
exception ParseError
exception Unknown
exception DSN
type t = (string, string) Hashtbl.t
(* Private : access to hashtbl *)
let get h k =
try Hashtbl.find h k
with Not_found -> raise Unknown
let getu h k =
try Hashtbl.find h k
with Not_found -> "unknown"
let check_policy pcy =
if getu pcy "request" = "smtpd_access_policy" then
pcy
else
raise ParseError
let domain s =
try
Str.string_after s ((String.index s '@')+1)
with Not_found -> raise Unknown (* case of rpcts / sender that are not email addresses *)
(* Private : log formats *)
let log_end pcy =
Printf.sprintf
"from=<%s> to=<%s> proto=%s helo=<%s>"
( getu pcy "sender" )
( getu pcy "recipient" )
( getu pcy "protocol_name" )
( getu pcy "helo_name" )
let log_start : t -> string = fun pcy ->
Printf.sprintf
"%s from %s[%s]"
( getu pcy "protocol_state" )
( getu pcy "client_name" )
( getu pcy "client_address" )
(* public *)
let read ic =
let res = Hashtbl.create 13 in
try
while true do
try
let line = input_line ic in
if String.length line = 0 then raise End_of_file;
let i = String.index line '=' in
Hashtbl.add res (Str.string_before line i) (Str.string_after line (i+1))
with Not_found -> raise ParseError
done;
assert false
with
| End_of_file -> check_policy res
let clear = Hashtbl.clear
let client_address h = get h "client_address"
let client_name h = get h "client_name"
let helo_name h = get h "helo_name"
let sender h =
try
(
match Hashtbl.find h "sender" with
| "" -> raise DSN
| s -> s
)
with Not_found -> raise DSN
let rcpt_domain h = domain (get h "recipient")
let sender_domain h = domain (sender h)
let log_format level answer pcy =
Printf.sprintf "%s: %s: %s; %s"
level (log_start pcy) answer (log_end pcy)
let spf_explain pcy =
let sender = getu pcy "sender" in
let ip = getu pcy "client_address" in
Printf.sprintf " - Please see http://spf.pobox.com/why.html?sender=%s&ip=%s" sender ip

View File

@ -0,0 +1,44 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
type t
exception ParseError
exception Unknown
exception DSN
val read : in_channel -> t
val clear : t -> unit
val client_address : t -> string
val client_name : t -> string
val sender : t -> string
val helo_name : t -> string
val rcpt_domain: t -> string
val sender_domain : t -> string
val log_format : string -> string -> t -> string
val spf_explain : t -> string

View File

@ -0,0 +1,106 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
open Policy
exception Dirty of string
exception Reject of string
type rhbl_type = Helo | Rcpt | Sender | Client
(* HELPERS *)
(** [rev_ip "ip1.ip2.ip3.ip4"] returns ["ip4.ip3.ip2.ip1"] *)
let rev_ip ip =
let l = Str.split (Str.regexp_string ".") ip in
String.concat "." (List.rev l)
(** [dns_check r i h] returns :
* [i+1] if ["$r.$h"] domain exists,
* [i] if domain does not exists,
* [i+1] if any error occur.
* This is meant to be use with List.fold_left
*)
let dns_check rad host =
try
let _ = Unix.gethostbyname (rad ^ "." ^ host) in
raise (Dirty ("blacklisted by " ^ host))
with
| Not_found -> ()
| Dirty _ as e -> raise e
| _ -> raise (Dirty "rbl failure")
let rhbl_extract_domain =
function
| Helo -> helo_name
| Rcpt -> helo_name
| Sender -> sender_domain
| Client -> client_name
(* PUBLIC INTERFACE *)
let check_rbl rbl_list pcy =
try
let revip = rev_ip (client_address pcy) in
List.iter (dns_check revip) rbl_list
with
| Policy.Unknown -> raise (Dirty "no `client_address' found")
| Policy.DSN -> ()
let check_rhbl kind rhbl_list pcy =
try
let host = (rhbl_extract_domain kind) pcy in
List.iter (dns_check host) rhbl_list
with
| Policy.Unknown ->
(
match kind with
| Helo | Rcpt -> raise (Dirty "no `helo_name' found")
| Sender -> raise (Dirty "no `sender_name' found")
| Client -> raise (Dirty "no `client_name' found")
)
| Policy.DSN -> ()
open Spf
type spf_mode = Spf_off | Spf_normal | Spf_strict | Spf_paranoid
let check_spf mode dorej pcy =
if mode != Spf_off then
let fail s =
if dorej then raise (Reject (s ^ Policy.spf_explain pcy)) else raise (Dirty s)
in try
match spf_query (sender pcy) (client_address pcy) (helo_name pcy) with
| SPF_pass -> ()
| SPF_softerr -> fail "SPF soft error"
| SPF_harderr -> fail "SPF hard error"
| SPF_none -> if mode != Spf_normal then raise (Dirty "no SPF record found")
| SPF_neutral -> if mode = Spf_paranoid then raise (Dirty "SPF neutral")
with
| Spf.Error -> raise (Dirty "SPF Internal error")
| Policy.DSN -> ()

View File

@ -0,0 +1,50 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
exception Dirty of string
exception Reject of string
type spf_mode = Spf_off | Spf_normal | Spf_strict | Spf_paranoid
type rhbl_type = Helo | Rcpt | Sender | Client
(**
* this module defines some rules that a Policy should verify to be whitelisted.
*
* quoting POSTFIX POLICY README [1] :
* In case of trouble the policy server must not send a reply.
* Instead the server must log a warning and disconnect.
* Postfix will retry the request at some later time.
*
* We DON'T follow that policy since we write a WHITELIST server.
* So our choice is that any problem is similar to a invalid Check.
*
* [1] http://www.postfix.org/SMTPD_POLICY_README.html
*)
val check_rbl : string list -> Policy.t -> unit
val check_rhbl : rhbl_type -> string list -> Policy.t -> unit
val check_spf : spf_mode -> bool -> Policy.t -> unit

View File

@ -0,0 +1,63 @@
##
## Example config for whitelister.
## put this file in /etc/whitelister.conf
##
################################################################################
#
# 1. DAEMON CONFIGURATION
#
################################################################################
# verb
# verbosity of the logs
# 0: `Clean' notifications are off
# 1: enable `Clean' notifications
# pidf
# path to the pidfile whitelister has to use
# default is /var/run/whitelister.pid
# examples:
# pidf: /dev/null
# sock
# socket the server has to listen to
# either unix or tcp socket are possible.
# syntax is ip:port (the :port is required) for tcp sockets
# default is 127.0.0.1:10000
# examples :
# sock: /var/spool/postfix/private/whitelister.ctl
# sock: 127.0.0.1:100
# user
# name of the user that whitelister will run under if launched from root
# default is nobody
user: nobody
# group
# name of the group that whitelister will run under if launched from root
# default is nogroup
group: nogroup
################################################################################
#
# 2. RULES CONFIGURATION
#
################################################################################
# rbl
# put one rbl per line
rbl: dynablock.njabl.org
rbl: dul.dnsbl.sorbs.net
# rhbl_* : rhbl_client / rhbl_sender / rhbl_rcpt / rhbl_helo
# put one rhbl per line
rhbl_sender: bogusmx.rfc-ignorant.org
# spf
# use spf diagnostics (default is 0)
# spf: 1
# spfrej
# what to do with spf rejects, default is nothing. ignored if spf is off
# spfrej: off

View File

@ -0,0 +1,173 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
(* types *)
type config = {
mutable pidf: string;
mutable sock: Unix.sockaddr;
mutable user: string;
mutable group: string;
mutable verb: int;
mutable spf: Rules.spf_mode;
mutable spfrej: bool;
mutable rbl: string list;
mutable rhbl_client: string list;
mutable rhbl_rcpt: string list;
mutable rhbl_sender: string list;
mutable rhbl_helo: string list;
}
(* Checker *)
let log msg =
let lg = Syslog.openlog ~facility:`LOG_MAIL ~flags:[`LOG_PID] "whitelister" in
Syslog.syslog lg `LOG_INFO msg;
Syslog.closelog lg
let log_event level answer pcy =
log (Policy.log_format level answer pcy)
let checker cfg s =
let oc = Unix.out_channel_of_descr s in
try
while true do
let pcy = Policy.read (Unix.in_channel_of_descr s) in
let ans = (
try
Rules.check_rbl cfg.rbl pcy;
Rules.check_rhbl Rules.Helo cfg.rhbl_helo pcy;
Rules.check_rhbl Rules.Sender cfg.rhbl_sender pcy;
Rules.check_rhbl Rules.Rcpt cfg.rhbl_rcpt pcy;
Rules.check_rhbl Rules.Client cfg.rhbl_client pcy;
Rules.check_spf cfg.spf cfg.spfrej pcy;
if cfg.verb > 0 then log_event "Clean" "OK" pcy;
"OK"
with
| Rules.Dirty s ->
log_event "Dirty" (Printf.sprintf "DUNNO (%s)" s) pcy;
"DUNNO"
| Rules.Reject s ->
let s' = "REJECT "^s in
log_event "Reject" s' pcy;
s'
) in
Printf.fprintf oc "action=%s\n\n" ans;
flush oc;
Policy.clear pcy (* not needed, but can help the GC *)
done
with _ -> Unix.shutdown s Unix.SHUTDOWN_ALL
(* Configuration *)
let empty_config () = {
pidf = "/var/run/whitelister.pid" ;
sock = Unix.ADDR_INET (Unix.inet_addr_of_string "127.0.0.1", 10000) ;
user = "nobody" ;
group = "nogroup" ;
verb = 0 ;
spf = Rules.Spf_off ;
spfrej = false;
rbl = [] ;
rhbl_client = [] ;
rhbl_rcpt = [] ;
rhbl_sender = [] ;
rhbl_helo = [] ;
}
let to_bool s =
function
| "0" | "off" | "no" -> false
| "1" | "yes" | "on" -> true
| _ -> prerr_endline ("cannot read a boolean value for `"^s^"', possible values are on/off, yes/no, 1/0"); exit 1
let update_config cfg file =
let ic = open_in file in
try
while true do
let line = input_line ic in
if String.length line > 0 && line.[0] <> '#' then
match Str.split (Str.regexp "[ \t:]+") line with
| [] -> ()
| ["pidf"; f] -> cfg.pidf <- f
| ["sock"; s] -> cfg.sock <- Unix.ADDR_UNIX s
| ["sock"; ip; p] -> cfg.sock <- Unix.ADDR_INET (Unix.inet_addr_of_string ip, int_of_string p)
| ["user"; u] -> cfg.user <- u
| ["group"; g] -> cfg.group <- g
| ["verb"; "0"] -> cfg.verb <- 0
| ["verb"; "1"] -> cfg.verb <- 1
| ["spf"; "0"] -> cfg.spf <- Rules.Spf_off
| ["spf"; "1"] -> cfg.spf <- Rules.Spf_normal
| ["spf"; "2"] -> cfg.spf <- Rules.Spf_strict
| ["spf"; "3"] -> cfg.spf <- Rules.Spf_paranoid
| ["spfrej"; b] -> cfg.spfrej <- to_bool "spfrej" b
| ["rbl"; h] -> cfg.rbl <- h::cfg.rbl
| ["rhbl_client"; h] -> cfg.rhbl_client <- h::cfg.rhbl_client
| ["rhbl_helo"; h] -> cfg.rhbl_helo <- h::cfg.rhbl_helo
| ["rhbl_rcpt"; h] -> cfg.rhbl_rcpt <- h::cfg.rhbl_rcpt
| ["rhbl_sender"; h] -> cfg.rhbl_sender <- h::cfg.rhbl_sender
(* deprecated settings *)
| ["rhbl"; h] -> prerr_endline "rhbl is deprecated, it defaults to rhbl_client which may not be what you want.";
cfg.rhbl_client <- h::cfg.rhbl_client
| _ -> prerr_string (Printf.sprintf "`%s' is not a valid config line\n" line)
done;
assert false
with End_of_file -> close_in ic; cfg
let read_config () =
let found_files = List.filter Sys.file_exists ["/etc/whitelister.conf" ; "whitelister.conf"] in
List.fold_left update_config (empty_config ()) found_files
(* Server thingies *)
open Server
let my_server_loop cfg sock =
pidfile_write ();
pidfile_close ();
log ("started, listening to " ^ (string_of_sockaddr cfg.sock));
while true do
let s = fst (unix_do Unix.accept sock) in
double_fork (fun s -> Unix.close sock; checker cfg s ; exit 0) s;
Unix.close s
done
(* Main LOOP *)
let _ =
let cfg = read_config () in
pidfile_open cfg.pidf;
pidfile_write ();
daemonize (cfg.user,cfg.group) (my_server_loop cfg) (bind_to_sock cfg.sock)

26
COPYING Normal file
View File

@ -0,0 +1,26 @@
Copyright (c) The Regents of the University of California.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

69
ChangeLog Normal file
View File

@ -0,0 +1,69 @@
whitelister 0.8
* relicense under BSD license. GPLv3 seems to be delirious wrt DRM, and I
mean whitelister to be as free as possible.
* rework DSN code fully, so that it won't allow DSN's to skip some tests.
* rework rhbl logging: on DSN, whitelister claimed client_name did not
exist, where it was in fact the sender that has no domain.
whitelister 0.7
* reorganize code so that privilege drop is done before exec.
(needed kludgy organization for the pidfile)
* put daemon independante code in server.ml
* close /dev/null after dup2 calls.
* DSN (mail from <> are mails we MUST accept).
whitelister 0.6
* reorganize logs a bit, copy postfix logs formats.
* SPF REJECT now build a message pointing to spf.pobox.com.
* Add setting `verb' in order to hide `Clean' log entries.
* Finer grained rhbl checkings :
rhbl_helo / rhbl_rcpt / rhbl_sender / rhbl_client.
* whitelister can answer to multiple requests:
dont close the socket ourselves, so that postfix can talk to us on the
same socket. postfix is now the one that will close the socket.
Bugfixes:
* Fix fd starvation introduced in 0.5.
whitelister 0.5.2 (the `never release too quickly' release)
* umask set to 0111 during socket bind, so that anybody can talk to it.
* shutdown the socket ... else it remains open, and makes postfix timeout.
whitelister 0.5
* rewrite the main server loop so that the privileges are drop just after
the bind/listen calls.
* better logging format : now use "$level ($hostname[$ip] $reason)", eg
`Dirty (foo.bar.org[1.2.3.4] blacklisted by rbl.quux.com)'.
whitelister 0.4
* create module Spf (against libspf).
* enable an spf check.
* allow whitelister to reject mails on unvalid SPF.
whitelister 0.3
* now use a pidfile.
* now daemonize itself in the background.
whitelister 0.2.1 (bugfix release)
* bug in logging: in case of an rbl failure, the error was masked.
whitelister 0.2
* add support for privilege drop:
- create user and group configs (defaults to nobody:nogroup).
* Logging facilities.
whitelister 0.1
* initial release.
vim: set ts=2 sts=2 sw=2 noet:

94
Makefile Normal file
View File

@ -0,0 +1,94 @@
##########################################################################
# #
# whitelister : a Whitelister Policy Daemon for Postfix #
# ~~~~~~~~~~~ #
# #
# Copyright (C) 2005 AAEGE.org #
# Author : Pierre Habouzit <pierre.habouzit@m4x.org> #
# ____________________________________________________________________ #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; either version 2 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the Free Software #
# Foundation, Inc., #
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
##########################################################################
VERSION = 0.8
PROGRAM = whitelister
PKG_DIST = $(PROGRAM)-$(VERSION)
REPO = $(shell svn info | grep URL | sed -e 's,URL *: *,,')
PKGS = -package syslog
CFLAGS = -O2 -Wall -fPIC
CCLIB = -cclib
CCOPT = $(foreach opt,$(CFLAGS),-ccopt $(opt))
OCAMLC = ocamlfind ocamlc $(PKGS) $(CCOPT) $(CCLIB)
OCAMLOPT= ocamlfind ocamlopt -linkpkg $(PKGS) $(CCOPT) $(CCLIB)
OCAMLDEP= ocamlfind ocamldep $(PKGS)
BIB = str.cmxa unix.cmxa syslog.cmxa
CMX = policy.cmx rules.cmx server.cmx whitelister.cmx
##############################################################
all: $(PROGRAM)
whitelister: $(CMX)
ocamlfind ocamlopt $(PKGS) $(CCOPT) -o $@ $(BIB) $^
headers: Makefile *.ml *.mli
headache -h tpl/header $^
dist:
@rm -rf $(PKG_DIST) $(PKG_DIST).tar.gz
@svn export $(REPO) $(PKG_DIST)
@rm -rf $(PKG_DIST)/debian
@tar czf $(PKG_DIST).tar.gz $(PKG_DIST)
@rm -rf $(PKG_DIST)
@echo -e "\ndistribution built in $(PKG_DIST).tar.gz\n"
##############################################################
.SUFFIXES: .mli .ml .cmi .cmo .cmx .mll .mly .c .o
.c.o:
$(OCAMLC) $(CCOPT) -c $<
.mli.cmi:
$(OCAMLC) -c $<
.ml.cmo:
$(OCAMLC) -c $<
.ml.cmx:
$(OCAMLOPT) -a str.cmxa -c $<
.mll.ml:
$(OCAMLLEX) $<
##############################################################
cleanbyte:
rm -rf *.{cm?,o} *~
clean: cleanbyte
rm -f .depend
rm -f $(PROGRAM)
.depend depend: *.ml *.mli
rm -f .depend
$(OCAMLDEP) *.ml *.mli > .depend
include .depend

159
README Normal file
View File

@ -0,0 +1,159 @@
whitelister is a Postfix Policy Server (see [1]).
the aim is to accept every really clean mail immediately, and to reserve the
evil treatments (like greylisting) to suspicious mails.
BUILDING WHITELISTER
~~~~~~~~~~~~~~~~~~~~
you need :
* a recent ocaml distribution (like 3.08.3), supporting Unix module and
native compilation.
* ocamlfind
* the package syslog
then you only have to run `make' and it should produce a `whitelister'
executable.
Note for Debian : if you have a deb-src line in your /etc/apt/source.list,
you can get the Build Dependecies of whitelister by simply running :
`apt-get build-dep whitelister'
POSTFIX CONFIGURATION
~~~~~~~~~~~~~~~~~~~~~
It is intended to be used like that :
(1) Default inet socket :
in your main.cf :
smtpd_recipient_restrictions =
...
reject_unauth_destination
check_policy_service inet:127.0.0.1:10000
... here your nasty treatments , like postgrey ...
in your whitelister.conf, don't specify sock as its default value is OK.
(2) Unix socket
if you use a unix socket (more efficient on most systems) :
smtpd_recipient_restrictions =
...
reject_unauth_destination
check_policy_service unix:private/whitelister.ctl
... here your nasty treatments , like postgrey ...
in your whitelister.conf, set :
sock: /var/spool/postfix/private/whitelister.ctl
Some Notes :
~~~~~~~~~~
Postfix DOC states :
In case of trouble the policy server must not send a reply. Instead the
server must log a warning and disconnect. Postfix will retry the
request at some later time.
But whitelister does not work that way. Either the mail seems to be clean
(wrt whitelister checks) and whitelister returns 'OK' (and the mail is also
accepted), or one check fails and it returns 'DUNNO' in order to let the
mail go through the nexts checks (like greylister).
That's why, you *have* to use whitelister as one of the *last* rules in your
smtpd_recipient_restrictions. Else, it can transform your smtp server into
an open-relay.
Any failure (in DNS e.g.) is also interpreted as a suspicious mail, and it
will also go through the next restrictions checks too.
WHITELISTER CONFIGURATION
~~~~~~~~~~~~~~~~~~~~~~~~~
whitelister search for two config files, named whitelister.conf in :
o /etc/whitelister.conf
o the same directory as itself
and it aggregates the configuration of both files.
the syntax is : « key: value » where key is the setting key, and value its
value without quotes or anything. Comments begin with a # at the first char
of the line see whitelister-example.conf for an example config file.
current settings are :
(1) Daemon Options
--------------
verb verbosity of the logs.
`0' means don't log `Clean' mails
`1' means log everything
pidf path to the pidfile whitelister will write its pid into.
default is /var/run/whitelister.pid ,
use /dev/null if you don't want to use the feature
sock the socket whitelister will listen to.
the value is the path for a unix socket,
or ip:port for a TCP one.
last setting is used (/etc/whitelister.conf overrides local configs)
user name of the user used to run whitelister if launched as root
default is nobody
group name of the group used to run whitelister if launched as root
default is nogroup
(2) Rules Configuration
-------------------
rbl hostname of a rbl service
rhbl_rcpt / rhbl_helo / rhbl_client / rhbl_sender
hostname of a rhbl service.
rcpt/helo/client/sender refers to the classical postfix
terminology related to the settings :
smtpd_(recipient, helo, client, sender)_restriction
it means that an rhbl check is performed on (resp.) :
- the domain of the recipient
- the domain from the HELO/EHLO command
- the domain of the client connection
- the domain part of the sender address
spf spf checkings :
0 means no check.
for the rest, see the table :
-------+-------+----------+----------------+-------
level | pass | neutal | empty record | other
-------+-------+----------+----------------+-------
1 | ok | ok | ok | !!!!
2 | ok | ok | !!!! | !!!!
3 | ok | !!!! | !!!! | !!!!
-------+-------+----------+----------------+-------
spfrej reject mail on invalid SPF (default is yes), possible values are
on/off/yes/no/0/1
APPENDIX
[1] http://www.postfix.org/SMTPD_POLICY_README.html

159
debian/changelog vendored Normal file
View File

@ -0,0 +1,159 @@
whitelister (0.8-5.evolix1) unstable; urgency=low
* Disable SPF.
* Test build with Wheezy.
* Switch to dpkg-source 3.0 (quilt) format
-- Gregory Colpart <reg@debian.org> Mon, 22 Sep 2014 14:05:25 +0200
whitelister (0.8-5.evolix) unstable; urgency=low
* Applied patch for supporting DNS verifications (Closes: #421501)
-- Gregory Colpart <reg@debian.org> Thu, 27 May 2010 19:34:34 +0200
whitelister (0.8-5) unstable; urgency=low
* Make the logcheck rules less leaky, thanks to Tim Small (Closes: #499499).
-- Pierre Habouzit <madcoder@debian.org> Thu, 25 Sep 2008 10:53:04 +0200
whitelister (0.8-4.1) unstable; urgency=low
* Non-maintainer upload to solve release goal.
* Add LSB dependency header to init.d scripts (Closes: #466659).
-- Petter Reinholdtsen <pere@debian.org> Sun, 30 Mar 2008 11:46:06 +0200
whitelister (0.8-4) unstable; urgency=low
* Update ignore.d/server rules to ignore normal traffic. We especially don't
wan't to monitor `Dirty' from logcheck as there is too many spam around
nowadays. (Closes: #376929).
* Bump Standards-Version to 3.7.2.
-- Pierre Habouzit <madcoder@debian.org> Sun, 7 Jan 2007 17:25:39 +0100
whitelister (0.8-3) unstable; urgency=high
* Prevent ftbfs by forcing make depend, urgency set to high as it prevents
ftbfs'es.
-- Pierre Habouzit <madcoder@debian.org> Sun, 5 Nov 2006 23:38:09 +0100
whitelister (0.8-2) unstable; urgency=low
* Add kfreebsd-amd64 to the architecture list (closes: #361631).
-- Pierre Habouzit <madcoder@debian.org> Sun, 5 Nov 2006 23:37:45 +0100
whitelister (0.8-1) unstable; urgency=low
* New upstream release (Note that whitelister is now under a BSD license).
-- Pierre Habouzit <madcoder@debian.org> Mon, 13 Feb 2006 23:16:17 +0100
whitelister (0.7-2) unstable; urgency=low
* Fix upstream bug : group/user inversion in server.ml.
-- Pierre Habouzit <madcoder@debian.org> Sat, 14 Jan 2006 16:10:59 +0100
whitelister (0.7-1) unstable; urgency=low
* New upstream release.
-- Pierre Habouzit <madcoder@debian.org> Sat, 14 Jan 2006 15:53:42 +0100
whitelister (0.6-2) unstable; urgency=low
* Update archs that have ocamlopt (closes: #347893).
-- Pierre Habouzit <madcoder@debian.org> Sat, 14 Jan 2006 13:06:19 +0100
whitelister (0.6-1) unstable; urgency=low
* New upstream release.
* Fixes FD starvation problem (closes: #325282).
-- Pierre Habouzit <madcoder@debian.org> Sun, 28 Aug 2005 11:15:58 +0200
whitelister (0.5.2-1) unstable; urgency=low
* New upstream release.
-- Pierre Habouzit <madcoder@debian.org> Wed, 24 Aug 2005 02:02:36 +0200
whitelister (0.5-1) unstable; urgency=low
* New upstream release.
* Server loop has been rewritten, so that privileges drop comme after socket
bind/listen (closes: #322582).
* Some improvements in the init script.
-- Pierre Habouzit <madcoder@debian.org> Tue, 23 Aug 2005 21:44:16 +0200
whitelister (0.4-4) unstable; urgency=low
* Fix a problem related to set -e in the init script that make the init
script misfunction in case of whitelister failure.
-- Pierre Habouzit <madcoder@debian.org> Tue, 23 Aug 2005 19:42:10 +0200
whitelister (0.4-3) unstable; urgency=low
* Add kfreebsd-i386 to the Arch list (closes: #315938).
* Bump Standards Version (no change required).
-- Pierre Habouzit <madcoder@debian.org> Tue, 19 Jul 2005 12:56:36 +0200
whitelister (0.4-2) unstable; urgency=low
* Only build the package where ocaml native compiler are supported.
-- Pierre Habouzit <madcoder@debian.org> Thu, 23 Jun 2005 14:01:47 +0200
whitelister (0.4-1) unstable; urgency=low
* First upload to debian (closes: #312309).
-- Pierre Habouzit <madcoder@debian.org> Tue, 7 Jun 2005 12:21:08 +0200
whitelister (0.3-2) UNRELEASED; urgency=low
* Add a logcheck rule to ignore Clean messages.
-- Pierre Habouzit <madcoder@debian.org> Fri, 20 May 2005 15:30:36 +0200
whitelister (0.3-1) UNRELEASED; urgency=low
* New upstram release.
* Use start-stop-daemon with pidfile since whitelister supports it now.
-- Pierre Habouzit <madcoder@debian.org> Fri, 20 May 2005 14:22:29 +0200
whitelister (0.2.1-1) UNRELEASED; urgency=low
* New upstream release.
-- Pierre Habouzit <madcoder@debian.org> Thu, 19 May 2005 08:28:07 +0200
whitelister (0.2-1) UNRELEASED; urgency=low
* New upstream release.
-- Pierre Habouzit <pierre.habouzit@m4x.org> Wed, 18 May 2005 14:10:34 +0200
whitelister (0.1-2) UNRELEASED; urgency=low
* Add a default empty config.
* Make whitelister a real daemon (live in sbin + init script).
-- Pierre Habouzit <pierre.habouzit@m4x.org> Tue, 17 May 2005 16:15:12 +0200
whitelister (0.1-1) UNRELEASED; urgency=low
* Initial Release.
-- Pierre Habouzit <pierre.habouzit@m4x.org> Tue, 17 May 2005 14:34:39 +0200

1
debian/compat vendored Normal file
View File

@ -0,0 +1 @@
5

16
debian/control vendored Normal file
View File

@ -0,0 +1,16 @@
Source: whitelister
Section: mail
Priority: optional
Maintainer: Pierre Habouzit <madcoder@debian.org>
Build-Depends: debhelper (>= 4.2.1), cdbs (>=0.4.26), ocaml-base-nox, ocaml-findlib, libsyslog-ocaml-dev
Standards-Version: 3.7.2
Package: whitelister
Architecture: alpha amd64 arm hurd-i386 i386 ia64 kfreebsd-i386 kfreebsd-amd64 powerpc sparc
Depends: ${shlibs:Depends}, ${misc:Depends}
Suggests: postgrey
Description: a Postfix Whitelister daemon
whitelister is a Postfix whitelister Policy Daemon aimed to whitelist mails
that come from clean hosts wrt rbl/rhbl in order to let suspicious mails go
through evil treatments like greylisting.

13
debian/copyright vendored Normal file
View File

@ -0,0 +1,13 @@
This package was debianized by Pierre Habouzit <x2000habouzit@murphy> on
Fri, 15 Apr 2005 12:52:37 +0200.
Copyright Holder: Pierre Habouzit
License: BSD License
The original source code for this package was downloaded from
https://projects.aaege.net/mailtools/wiki/Project.Whitelister
On Debian GNU/Linux systems the complete text of the BSD can be found
in /usr/share/common-licenses/BSD

270
debian/patches/disable_spf.patch vendored Normal file
View File

@ -0,0 +1,270 @@
Index: whitelister-0.8/Makefile
===================================================================
--- whitelister-0.8.orig/Makefile 2014-09-22 16:12:20.655776945 +0200
+++ whitelister-0.8/Makefile 2014-09-22 16:17:09.571315293 +0200
@@ -31,23 +31,22 @@
PKGS = -package syslog
CFLAGS = -O2 -Wall -fPIC
-CCLIB = -cclib -lspf
+CCLIB = -cclib
CCOPT = $(foreach opt,$(CFLAGS),-ccopt $(opt))
OCAMLC = ocamlfind ocamlc $(PKGS) $(CCOPT) $(CCLIB)
-OCAMLOPT= ocamlfind ocamlopt $(PKGS) $(CCOPT) $(CCLIB)
+OCAMLOPT= ocamlfind ocamlopt -linkpkg $(PKGS) $(CCOPT) $(CCLIB)
OCAMLDEP= ocamlfind ocamldep $(PKGS)
BIB = str.cmxa unix.cmxa syslog.cmxa
-CMX = spf.cmx policy.cmx rules.cmx server.cmx whitelister.cmx
-COB = spfstubs.o
+CMX = policy.cmx rules.cmx server.cmx whitelister.cmx
##############################################################
all: $(PROGRAM)
-whitelister: $(COB) $(CMX)
- $(OCAMLOPT) -o $@ $(BIB) $^
+whitelister: $(CMX)
+ ocamlfind ocamlopt $(PKGS) $(CCOPT) -o $@ $(BIB) $^
headers: Makefile *.ml *.mli
headache -h tpl/header $^
@@ -75,7 +74,7 @@
$(OCAMLC) -c $<
.ml.cmx:
- $(OCAMLOPT) -c $<
+ $(OCAMLOPT) -a str.cmxa -c $<
.mll.ml:
$(OCAMLLEX) $<
Index: whitelister-0.8/spf.ml
===================================================================
--- whitelister-0.8.orig/spf.ml 2014-09-22 16:12:20.655776945 +0200
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,41 +0,0 @@
-(**************************************************************************)
-(* *)
-(* whitelister : a Whitelister Policy Daemon for Postfix *)
-(* ~~~~~~~~~~~ *)
-(* *)
-(* Copyright (C) 2005 AAEGE.org *)
-(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
-(* ____________________________________________________________________ *)
-(* *)
-(* This program is free software; you can redistribute it and/or modify *)
-(* it under the terms of the GNU General Public License as published by *)
-(* the Free Software Foundation; either version 2 of the License, or *)
-(* (at your option) any later version. *)
-(* *)
-(* This program is distributed in the hope that it will be useful, *)
-(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
-(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
-(* GNU General Public License for more details. *)
-(* *)
-(* You should have received a copy of the GNU General Public License *)
-(* along with this program; if not, write to the Free Software *)
-(* Foundation, Inc., *)
-(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
-(* *)
-(**************************************************************************)
-
-exception Error
-type spf_result =
- SPF_pass | SPF_neutral | SPF_none | SPF_softerr | SPF_harderr
-
-external _spf_query : string -> string -> string -> int = "spf_query"
-
-let spf_query host ip helo =
- match _spf_query host ip helo with
- | 0 -> SPF_pass
- | 1 -> SPF_neutral
- | 2 -> SPF_none
- | 3 -> SPF_softerr
- | 4 -> SPF_harderr
- | _ -> raise Error
-
Index: whitelister-0.8/spf.mli
===================================================================
--- whitelister-0.8.orig/spf.mli 2014-09-22 16:12:20.655776945 +0200
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,33 +0,0 @@
-(**************************************************************************)
-(* *)
-(* whitelister : a Whitelister Policy Daemon for Postfix *)
-(* ~~~~~~~~~~~ *)
-(* *)
-(* Copyright (C) 2005 AAEGE.org *)
-(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
-(* ____________________________________________________________________ *)
-(* *)
-(* This program is free software; you can redistribute it and/or modify *)
-(* it under the terms of the GNU General Public License as published by *)
-(* the Free Software Foundation; either version 2 of the License, or *)
-(* (at your option) any later version. *)
-(* *)
-(* This program is distributed in the hope that it will be useful, *)
-(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
-(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
-(* GNU General Public License for more details. *)
-(* *)
-(* You should have received a copy of the GNU General Public License *)
-(* along with this program; if not, write to the Free Software *)
-(* Foundation, Inc., *)
-(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
-(* *)
-(**************************************************************************)
-
-exception Error
-
-type spf_result =
- SPF_pass | SPF_neutral | SPF_none | SPF_softerr | SPF_harderr
-
-val spf_query : string -> string -> string -> spf_result
-
Index: whitelister-0.8/policy.ml
===================================================================
--- whitelister-0.8.orig/policy.ml 2014-09-22 16:12:20.655776945 +0200
+++ whitelister-0.8/policy.ml 2014-09-22 16:12:25.000000000 +0200
@@ -110,8 +110,3 @@
let log_format level answer pcy =
Printf.sprintf "%s: %s: %s; %s"
level (log_start pcy) answer (log_end pcy)
-
-let spf_explain pcy =
- let sender = getu pcy "sender" in
- let ip = getu pcy "client_address" in
- Printf.sprintf " - Please see http://spf.pobox.com/why.html?sender=%s&ip=%s" sender ip
Index: whitelister-0.8/policy.mli
===================================================================
--- whitelister-0.8.orig/policy.mli 2014-09-22 16:12:20.655776945 +0200
+++ whitelister-0.8/policy.mli 2014-09-22 16:12:25.000000000 +0200
@@ -42,4 +42,3 @@
val sender_domain : t -> string
val log_format : string -> string -> t -> string
-val spf_explain : t -> string
Index: whitelister-0.8/rules.ml
===================================================================
--- whitelister-0.8.orig/rules.ml 2014-09-22 16:12:20.655776945 +0200
+++ whitelister-0.8/rules.ml 2014-09-22 16:12:25.000000000 +0200
@@ -85,25 +85,6 @@
)
| Policy.DSN -> ()
-open Spf
-
-type spf_mode = Spf_off | Spf_normal | Spf_strict | Spf_paranoid
-
-let check_spf mode dorej pcy =
- if mode != Spf_off then
- let fail s =
- if dorej then raise (Reject (s ^ Policy.spf_explain pcy)) else raise (Dirty s)
- in try
- match spf_query (sender pcy) (client_address pcy) (helo_name pcy) with
- | SPF_pass -> ()
- | SPF_softerr -> fail "SPF soft error"
- | SPF_harderr -> fail "SPF hard error"
- | SPF_none -> if mode != Spf_normal then raise (Dirty "no SPF record found")
- | SPF_neutral -> if mode = Spf_paranoid then raise (Dirty "SPF neutral")
- with
- | Spf.Error -> raise (Dirty "SPF Internal error")
- | Policy.DSN -> ()
-
let check_dns_client dorej pcy =
if dorej then
if (client_name pcy) = "unknown" then raise (Dirty "no client_name (reject_unknown_client)")
Index: whitelister-0.8/spfstubs.c
===================================================================
--- whitelister-0.8.orig/spfstubs.c 2014-09-22 16:12:20.655776945 +0200
+++ /dev/null 1970-01-01 00:00:00.000000000 +0000
@@ -1,42 +0,0 @@
-#include <caml/mlvalues.h>
-#include <caml/memory.h>
-
-#include <spf.h>
-
-value spf_query(value from, value ip, value helo)
-{
- int res;
- peer_info_t* peer_info;
-
- CAMLparam3(from, ip, helo);
-
- peer_info = SPF_init("whitelister", String_val(ip), NULL, NULL, NULL, 0, 0);
- SPF_smtp_helo(peer_info, String_val(helo));
- SPF_smtp_from(peer_info, String_val(from));
- res = peer_info->RES = SPF_policy_main(peer_info);
-
- SPF_close(peer_info);
-
- switch(res)
- {
- case SPF_PASS:
- CAMLreturn(Val_int(0));
-
- case SPF_NEUTRAL:
- CAMLreturn(Val_int(1));
-
- case SPF_NONE:
- CAMLreturn(Val_int(2));
-
- case SPF_S_FAIL:
- CAMLreturn(Val_int(3));
-
- case SPF_H_FAIL:
- CAMLreturn(Val_int(4));
-
- default: /* SPF_ERROR, SPF_UNKNOWN, SPF_UNMECH */
- CAMLreturn(Val_int(5));
-
- }
-}
-
Index: whitelister-0.8/whitelister.ml
===================================================================
--- whitelister-0.8.orig/whitelister.ml 2014-09-22 16:12:20.655776945 +0200
+++ whitelister-0.8/whitelister.ml 2014-09-22 16:12:25.000000000 +0200
@@ -33,9 +33,6 @@
mutable group: string;
mutable verb: int;
- mutable spf: Rules.spf_mode;
- mutable spfrej: bool;
-
mutable rbl: string list;
mutable rhbl_client: string list;
mutable rhbl_rcpt: string list;
@@ -68,7 +65,6 @@
Rules.check_rhbl Rules.Sender cfg.rhbl_sender pcy;
Rules.check_rhbl Rules.Rcpt cfg.rhbl_rcpt pcy;
Rules.check_rhbl Rules.Client cfg.rhbl_client pcy;
- Rules.check_spf cfg.spf cfg.spfrej pcy;
Rules.check_dns_client cfg.dns_client pcy;
Rules.check_dns_rev_client cfg.dns_rev_client pcy;
if cfg.verb > 0 then log_event "Clean" "OK" pcy;
@@ -97,9 +93,6 @@
group = "nogroup" ;
verb = 0 ;
- spf = Rules.Spf_off ;
- spfrej = false;
-
rbl = [] ;
rhbl_client = [] ;
rhbl_rcpt = [] ;
@@ -133,12 +126,6 @@
| ["verb"; "0"] -> cfg.verb <- 0
| ["verb"; "1"] -> cfg.verb <- 1
- | ["spf"; "0"] -> cfg.spf <- Rules.Spf_off
- | ["spf"; "1"] -> cfg.spf <- Rules.Spf_normal
- | ["spf"; "2"] -> cfg.spf <- Rules.Spf_strict
- | ["spf"; "3"] -> cfg.spf <- Rules.Spf_paranoid
- | ["spfrej"; b] -> cfg.spfrej <- to_bool "spfrej" b
-
| ["rbl"; h] -> cfg.rbl <- h::cfg.rbl
| ["rhbl_client"; h] -> cfg.rhbl_client <- h::cfg.rhbl_client
| ["rhbl_helo"; h] -> cfg.rhbl_helo <- h::cfg.rhbl_helo

View File

@ -0,0 +1,131 @@
--- whitelister-0.8.orig/whitelister-example.conf
+++ whitelister-0.8/whitelister-example.conf
@@ -61,3 +61,8 @@
# spfrej
# what to do with spf rejects, default is nothing. ignored if spf is off
# spfrej: off
+
+# dns
+# support dns verifications (default is 0)
+# dns_client: 1
+# dns_rev_client: 1
--- whitelister-0.8.orig/rules.mli
+++ whitelister-0.8/rules.mli
@@ -48,3 +48,7 @@
val check_rhbl : rhbl_type -> string list -> Policy.t -> unit
val check_spf : spf_mode -> bool -> Policy.t -> unit
+
+val check_dns_client : bool -> Policy.t -> unit
+
+val check_dns_rev_client : bool -> Policy.t -> unit
--- whitelister-0.8.orig/rules.ml
+++ whitelister-0.8/rules.ml
@@ -104,3 +104,13 @@
| Spf.Error -> raise (Dirty "SPF Internal error")
| Policy.DSN -> ()
+let check_dns_client dorej pcy =
+ if dorej then
+ if (client_name pcy) = "unknown" then raise (Dirty "no client_name (reject_unknown_client)")
+ else ()
+
+let check_dns_rev_client dorej pcy =
+ if dorej then
+ if (reverse_client_name pcy) = "unknown" then raise (Dirty "no reverse_client_name (reject_unknown_reverse_client)")
+ else ()
+
--- whitelister-0.8.orig/Makefile
+++ whitelister-0.8/Makefile
@@ -85,6 +85,7 @@
rm -rf *.{cm?,o} *~
clean: cleanbyte
+ rm -f .depend
rm -f $(PROGRAM)
.depend depend: *.ml *.mli
@@ -92,4 +93,3 @@
$(OCAMLDEP) *.ml *.mli > .depend
include .depend
-
--- whitelister-0.8.orig/whitelister.ml
+++ whitelister-0.8/whitelister.ml
@@ -41,6 +41,9 @@
mutable rhbl_rcpt: string list;
mutable rhbl_sender: string list;
mutable rhbl_helo: string list;
+
+ mutable dns_client: bool;
+ mutable dns_rev_client: bool;
}
(* Checker *)
@@ -65,7 +68,9 @@
Rules.check_rhbl Rules.Sender cfg.rhbl_sender pcy;
Rules.check_rhbl Rules.Rcpt cfg.rhbl_rcpt pcy;
Rules.check_rhbl Rules.Client cfg.rhbl_client pcy;
- Rules.check_spf cfg.spf cfg.spfrej pcy;
+ Rules.check_spf cfg.spf cfg.spfrej pcy;
+ Rules.check_dns_client cfg.dns_client pcy;
+ Rules.check_dns_rev_client cfg.dns_rev_client pcy;
if cfg.verb > 0 then log_event "Clean" "OK" pcy;
"OK"
with
@@ -100,6 +105,9 @@
rhbl_rcpt = [] ;
rhbl_sender = [] ;
rhbl_helo = [] ;
+
+ dns_client = false;
+ dns_rev_client = false;
}
let to_bool s =
@@ -137,6 +145,9 @@
| ["rhbl_rcpt"; h] -> cfg.rhbl_rcpt <- h::cfg.rhbl_rcpt
| ["rhbl_sender"; h] -> cfg.rhbl_sender <- h::cfg.rhbl_sender
+ | ["dns_client"; d] -> cfg.dns_client <- to_bool "dns_client" d
+ | ["dns_rev_client"; e] -> cfg.dns_rev_client <- to_bool "dns_rev_client" e
+
(* deprecated settings *)
| ["rhbl"; h] -> prerr_endline "rhbl is deprecated, it defaults to rhbl_client which may not be what you want.";
cfg.rhbl_client <- h::cfg.rhbl_client
--- whitelister-0.8.orig/policy.ml
+++ whitelister-0.8/policy.ml
@@ -64,10 +64,11 @@
let log_start : t -> string = fun pcy ->
Printf.sprintf
- "%s from %s[%s]"
- ( getu pcy "protocol_state" )
- ( getu pcy "client_name" )
- ( getu pcy "client_address" )
+ "%s from %s[%s][%s]"
+ ( getu pcy "protocol_state" )
+ ( getu pcy "client_name" )
+ ( getu pcy "client_address" )
+ ( getu pcy "reverse_client_name" )
(* public *)
@@ -91,6 +92,7 @@
let client_address h = get h "client_address"
let client_name h = get h "client_name"
+let reverse_client_name h = get h "reverse_client_name"
let helo_name h = get h "helo_name"
let sender h =
try
--- whitelister-0.8.orig/policy.mli
+++ whitelister-0.8/policy.mli
@@ -35,6 +35,7 @@
val client_address : t -> string
val client_name : t -> string
+val reverse_client_name : t -> string
val sender : t -> string
val helo_name : t -> string
val rcpt_domain: t -> string

2
debian/patches/series vendored Normal file
View File

@ -0,0 +1,2 @@
reject_unknown_client.patch
disable_spf.patch

17
debian/rules vendored Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/make -f
# -*- makefile -*-
DEB_DH_INSTALLINIT_ARGS := -- defaults 19
include /usr/share/cdbs/1/class/makefile.mk
include /usr/share/cdbs/1/rules/debhelper.mk
DEB_MAKE_CLEAN_TARGET := clean
DEB_MAKE_BUILD_TARGET := all
DEB_MAKE_INSTALL_TARGET :=
DEB_INSTALL_DOCS_ALL := README
DEB_INSTALL_CHANGELOG_ALL := ChangeLog
common-build-arch common-build-indep::
$(MAKE) depend

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (quilt)

23
debian/whitelister.conf vendored Normal file
View File

@ -0,0 +1,23 @@
##
## configuration for whitelister
##
## sock
## socket the server has to listen to
## either unix or tcp socket are possible.
## syntax is ip:port (the :port is required) for tcp sockets
## defaults : 127.0.0.1:10000
#sock: 127.0.0.1:10000
## rbl
## put one rbl per line
#rbl: dynablock.njabl.org
#rbl: dul.dnsbl.sorbs.net
## rhbls
## put one rhbl per line
#rhbl: bogusmx.rfc-ignorant.org

73
debian/whitelister.init vendored Normal file
View File

@ -0,0 +1,73 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: whitelister
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# X-Start-Before: postfix
# X-Stop-After: postfix
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
### END INIT INFO
#
# whitelister start/stop the whitelister deamon for postfix
# (priority should be smaller than that of postfix)
#
# Author: (c)2005 Pierre Habouzit <madcoder@debian.org>
# Based on Debian sarge's 'skeleton' example
# Distribute and/or modify at will.
#
# Version: $Id: whitelister.init,v 1.3 2005/05/20 13:13:14 x2000habouzit Exp $
#
set -e
PATH=/sbin:/bin:/usr/sbin:/usr/bin
DESC="postfix whitelister daemon"
NAME=whitelister
DAEMON=/usr/sbin/$NAME
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
# Gracefully exit if the package has been removed.
test -x $DAEMON || exit 0
d_start() {
start-stop-daemon --start --quiet --pidfile $PIDFILE \
--exec $DAEMON || echo -n " (already running)"
}
d_stop() {
start-stop-daemon --stop --oknodo --quiet --pidfile $PIDFILE \
--name $NAME
rm -f $PIDFILE
}
d_restart() {
d_stop
sleep 1
d_start
}
case "$1" in
start)
echo -n "Starting $DESC: $NAME"
d_start
echo "."
;;
stop)
echo -n "Stopping $DESC: $NAME"
d_stop
echo "."
;;
force-reload|restart)
echo -n "Restarting $DESC: $NAME"
d_restart
echo "."
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
exit 1
;;
esac
exit 0

3
debian/whitelister.install vendored Normal file
View File

@ -0,0 +1,3 @@
whitelister-example.conf /usr/share/doc/whitelister/examples/
whitelister /usr/sbin/
debian/whitelister.conf /etc/

View File

@ -0,0 +1,4 @@
^\w{3} [ :0-9]{11} [._[:alnum:]-]+ whitelister\[[0-9]+\]: Clean$
^\w{3} [ :0-9]{11} [._[:alnum:]-]+ whitelister\[[0-9]+\]: listening to inet:[:.[:xdigit:]]+:[[:digit:]]$
^\w{3} [ :0-9]{11} [._[:alnum:]-]+ whitelister\[[0-9]+\]: listening to unix:[-/._[:alnum:]]+$
^\w{3} [ :0-9]{11} [._[:alnum:]-]+ whitelister\[[0-9]+\]: (Dirty|Reject): RCPT from [._[:alnum:]-]+\[([0-9.]{7,15}|[0-9a-fA-F:.]{4,39})\]: (DUNNO \(.*\)|REJECT SPF.*); from=<.*> to=<.*> proto=E?SMTP helo=<.*>$

112
policy.ml Normal file
View File

@ -0,0 +1,112 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
exception ParseError
exception Unknown
exception DSN
type t = (string, string) Hashtbl.t
(* Private : access to hashtbl *)
let get h k =
try Hashtbl.find h k
with Not_found -> raise Unknown
let getu h k =
try Hashtbl.find h k
with Not_found -> "unknown"
let check_policy pcy =
if getu pcy "request" = "smtpd_access_policy" then
pcy
else
raise ParseError
let domain s =
try
Str.string_after s ((String.index s '@')+1)
with Not_found -> raise Unknown (* case of rpcts / sender that are not email addresses *)
(* Private : log formats *)
let log_end pcy =
Printf.sprintf
"from=<%s> to=<%s> proto=%s helo=<%s>"
( getu pcy "sender" )
( getu pcy "recipient" )
( getu pcy "protocol_name" )
( getu pcy "helo_name" )
let log_start : t -> string = fun pcy ->
Printf.sprintf
"%s from %s[%s][%s]"
( getu pcy "protocol_state" )
( getu pcy "client_name" )
( getu pcy "client_address" )
( getu pcy "reverse_client_name" )
(* public *)
let read ic =
let res = Hashtbl.create 13 in
try
while true do
try
let line = input_line ic in
if String.length line = 0 then raise End_of_file;
let i = String.index line '=' in
Hashtbl.add res (Str.string_before line i) (Str.string_after line (i+1))
with Not_found -> raise ParseError
done;
assert false
with
| End_of_file -> check_policy res
let clear = Hashtbl.clear
let client_address h = get h "client_address"
let client_name h = get h "client_name"
let reverse_client_name h = get h "reverse_client_name"
let helo_name h = get h "helo_name"
let sender h =
try
(
match Hashtbl.find h "sender" with
| "" -> raise DSN
| s -> s
)
with Not_found -> raise DSN
let rcpt_domain h = domain (get h "recipient")
let sender_domain h = domain (sender h)
let log_format level answer pcy =
Printf.sprintf "%s: %s: %s; %s"
level (log_start pcy) answer (log_end pcy)

44
policy.mli Normal file
View File

@ -0,0 +1,44 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
type t
exception ParseError
exception Unknown
exception DSN
val read : in_channel -> t
val clear : t -> unit
val client_address : t -> string
val client_name : t -> string
val reverse_client_name : t -> string
val sender : t -> string
val helo_name : t -> string
val rcpt_domain: t -> string
val sender_domain : t -> string
val log_format : string -> string -> t -> string

97
rules.ml Normal file
View File

@ -0,0 +1,97 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
open Policy
exception Dirty of string
exception Reject of string
type rhbl_type = Helo | Rcpt | Sender | Client
(* HELPERS *)
(** [rev_ip "ip1.ip2.ip3.ip4"] returns ["ip4.ip3.ip2.ip1"] *)
let rev_ip ip =
let l = Str.split (Str.regexp_string ".") ip in
String.concat "." (List.rev l)
(** [dns_check r i h] returns :
* [i+1] if ["$r.$h"] domain exists,
* [i] if domain does not exists,
* [i+1] if any error occur.
* This is meant to be use with List.fold_left
*)
let dns_check rad host =
try
let _ = Unix.gethostbyname (rad ^ "." ^ host) in
raise (Dirty ("blacklisted by " ^ host))
with
| Not_found -> ()
| Dirty _ as e -> raise e
| _ -> raise (Dirty "rbl failure")
let rhbl_extract_domain =
function
| Helo -> helo_name
| Rcpt -> helo_name
| Sender -> sender_domain
| Client -> client_name
(* PUBLIC INTERFACE *)
let check_rbl rbl_list pcy =
try
let revip = rev_ip (client_address pcy) in
List.iter (dns_check revip) rbl_list
with
| Policy.Unknown -> raise (Dirty "no `client_address' found")
| Policy.DSN -> ()
let check_rhbl kind rhbl_list pcy =
try
let host = (rhbl_extract_domain kind) pcy in
List.iter (dns_check host) rhbl_list
with
| Policy.Unknown ->
(
match kind with
| Helo | Rcpt -> raise (Dirty "no `helo_name' found")
| Sender -> raise (Dirty "no `sender_name' found")
| Client -> raise (Dirty "no `client_name' found")
)
| Policy.DSN -> ()
let check_dns_client dorej pcy =
if dorej then
if (client_name pcy) = "unknown" then raise (Dirty "no client_name (reject_unknown_client)")
else ()
let check_dns_rev_client dorej pcy =
if dorej then
if (reverse_client_name pcy) = "unknown" then raise (Dirty "no reverse_client_name (reject_unknown_reverse_client)")
else ()

54
rules.mli Normal file
View File

@ -0,0 +1,54 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
exception Dirty of string
exception Reject of string
type spf_mode = Spf_off | Spf_normal | Spf_strict | Spf_paranoid
type rhbl_type = Helo | Rcpt | Sender | Client
(**
* this module defines some rules that a Policy should verify to be whitelisted.
*
* quoting POSTFIX POLICY README [1] :
* In case of trouble the policy server must not send a reply.
* Instead the server must log a warning and disconnect.
* Postfix will retry the request at some later time.
*
* We DON'T follow that policy since we write a WHITELIST server.
* So our choice is that any problem is similar to a invalid Check.
*
* [1] http://www.postfix.org/SMTPD_POLICY_README.html
*)
val check_rbl : string list -> Policy.t -> unit
val check_rhbl : rhbl_type -> string list -> Policy.t -> unit
val check_spf : spf_mode -> bool -> Policy.t -> unit
val check_dns_client : bool -> Policy.t -> unit
val check_dns_rev_client : bool -> Policy.t -> unit

96
server.ml Normal file
View File

@ -0,0 +1,96 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
open Unix
let string_of_sockaddr = function
| ADDR_UNIX u -> "unix:"^u
| ADDR_INET (a,p) -> Printf.sprintf "inet:%s:%i" (string_of_inet_addr a) p
let rec unix_do f x =
try f x
with Unix_error(EINTR, _, _) -> unix_do f x
(* pid file stuff *)
let pidfile_fd = ref None
let pidfile_close () =
match !pidfile_fd with
| Some f -> close f; pidfile_fd := None
| None -> ()
let pidfile_open file =
try
pidfile_close ();
pidfile_fd := Some (openfile file [O_WRONLY;O_CREAT;O_TRUNC] 0o644)
with Sys_error _ ->
prerr_endline ("Cannot write my pid in the pidfile "^file);
exit 1
let pidfile_write () =
match !pidfile_fd with
| None ->
prerr_endline ("pidfile is not open, call pidfile_open before pidfile_write");
exit 1
| Some fd ->
ignore (lseek fd 0 SEEK_SET);
ftruncate fd 0;
let pid = Printf.sprintf "%i\n" (getpid()) in
ignore (unix_do (single_write fd pid 0) (String.length pid))
(* server stuff *)
let drop_privs ( user, group ) =
if geteuid () = 0 then (
setgid (getgrnam group).gr_gid;
setuid (getpwnam user).pw_uid
)
let bind_to_sock sockaddr =
(match sockaddr with ADDR_UNIX u -> (try unlink u with _ -> ()) | _ -> ());
let mask = umask 0o111 in
let sock = socket (domain_of_sockaddr sockaddr) SOCK_STREAM 0 in
setsockopt sock SO_REUSEADDR true;
bind sock sockaddr;
listen sock 0;
let _ = umask mask in
sock
let double_fork f x =
match fork () with
| 0 -> if fork () <> 0 then exit 0; f x
| id -> ignore (waitpid [] id)
let daemonize runas server_loop arg =
drop_privs runas;
let dev_null = openfile "/dev/null" [O_WRONLY] 0o666 in
close stdin;
dup2 dev_null stdout;
dup2 dev_null stderr;
close dev_null;
double_fork server_loop arg

39
server.mli Normal file
View File

@ -0,0 +1,39 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
val string_of_sockaddr : Unix.sockaddr -> string
val unix_do : ('a -> 'b) -> 'a -> 'b
val pidfile_open : string -> unit
val pidfile_write : unit -> unit
val pidfile_close : unit -> unit
val drop_privs : string * string -> unit
val bind_to_sock : Unix.sockaddr -> Unix.file_descr
val double_fork : ('a -> unit) -> 'a -> unit
val daemonize : string * string -> ('a -> unit) -> 'a -> unit

23
tpl/header Normal file
View File

@ -0,0 +1,23 @@
whitelister : a Whitelister Policy Daemon for Postfix
~~~~~~~~~~~
Copyright (C) 2005 AAEGE.org
Author : Pierre Habouzit <pierre.habouzit@m4x.org>
____________________________________________________________________
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

68
whitelister-example.conf Normal file
View File

@ -0,0 +1,68 @@
##
## Example config for whitelister.
## put this file in /etc/whitelister.conf
##
################################################################################
#
# 1. DAEMON CONFIGURATION
#
################################################################################
# verb
# verbosity of the logs
# 0: `Clean' notifications are off
# 1: enable `Clean' notifications
# pidf
# path to the pidfile whitelister has to use
# default is /var/run/whitelister.pid
# examples:
# pidf: /dev/null
# sock
# socket the server has to listen to
# either unix or tcp socket are possible.
# syntax is ip:port (the :port is required) for tcp sockets
# default is 127.0.0.1:10000
# examples :
# sock: /var/spool/postfix/private/whitelister.ctl
# sock: 127.0.0.1:100
# user
# name of the user that whitelister will run under if launched from root
# default is nobody
user: nobody
# group
# name of the group that whitelister will run under if launched from root
# default is nogroup
group: nogroup
################################################################################
#
# 2. RULES CONFIGURATION
#
################################################################################
# rbl
# put one rbl per line
rbl: dynablock.njabl.org
rbl: dul.dnsbl.sorbs.net
# rhbl_* : rhbl_client / rhbl_sender / rhbl_rcpt / rhbl_helo
# put one rhbl per line
rhbl_sender: bogusmx.rfc-ignorant.org
# spf
# use spf diagnostics (default is 0)
# spf: 1
# spfrej
# what to do with spf rejects, default is nothing. ignored if spf is off
# spfrej: off
# dns
# support dns verifications (default is 0)
# dns_client: 1
# dns_rev_client: 1

171
whitelister.ml Normal file
View File

@ -0,0 +1,171 @@
(**************************************************************************)
(* *)
(* whitelister : a Whitelister Policy Daemon for Postfix *)
(* ~~~~~~~~~~~ *)
(* *)
(* Copyright (C) 2005 AAEGE.org *)
(* Author : Pierre Habouzit <pierre.habouzit@m4x.org> *)
(* ____________________________________________________________________ *)
(* *)
(* This program is free software; you can redistribute it and/or modify *)
(* it under the terms of the GNU General Public License as published by *)
(* the Free Software Foundation; either version 2 of the License, or *)
(* (at your option) any later version. *)
(* *)
(* This program is distributed in the hope that it will be useful, *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)
(* GNU General Public License for more details. *)
(* *)
(* You should have received a copy of the GNU General Public License *)
(* along with this program; if not, write to the Free Software *)
(* Foundation, Inc., *)
(* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *)
(* *)
(**************************************************************************)
(* types *)
type config = {
mutable pidf: string;
mutable sock: Unix.sockaddr;
mutable user: string;
mutable group: string;
mutable verb: int;
mutable rbl: string list;
mutable rhbl_client: string list;
mutable rhbl_rcpt: string list;
mutable rhbl_sender: string list;
mutable rhbl_helo: string list;
mutable dns_client: bool;
mutable dns_rev_client: bool;
}
(* Checker *)
let log msg =
let lg = Syslog.openlog ~facility:`LOG_MAIL ~flags:[`LOG_PID] "whitelister" in
Syslog.syslog lg `LOG_INFO msg;
Syslog.closelog lg
let log_event level answer pcy =
log (Policy.log_format level answer pcy)
let checker cfg s =
let oc = Unix.out_channel_of_descr s in
try
while true do
let pcy = Policy.read (Unix.in_channel_of_descr s) in
let ans = (
try
Rules.check_rbl cfg.rbl pcy;
Rules.check_rhbl Rules.Helo cfg.rhbl_helo pcy;
Rules.check_rhbl Rules.Sender cfg.rhbl_sender pcy;
Rules.check_rhbl Rules.Rcpt cfg.rhbl_rcpt pcy;
Rules.check_rhbl Rules.Client cfg.rhbl_client pcy;
Rules.check_dns_client cfg.dns_client pcy;
Rules.check_dns_rev_client cfg.dns_rev_client pcy;
if cfg.verb > 0 then log_event "Clean" "OK" pcy;
"OK"
with
| Rules.Dirty s ->
log_event "Dirty" (Printf.sprintf "DUNNO (%s)" s) pcy;
"DUNNO"
| Rules.Reject s ->
let s' = "REJECT "^s in
log_event "Reject" s' pcy;
s'
) in
Printf.fprintf oc "action=%s\n\n" ans;
flush oc;
Policy.clear pcy (* not needed, but can help the GC *)
done
with _ -> Unix.shutdown s Unix.SHUTDOWN_ALL
(* Configuration *)
let empty_config () = {
pidf = "/var/run/whitelister.pid" ;
sock = Unix.ADDR_INET (Unix.inet_addr_of_string "127.0.0.1", 10000) ;
user = "nobody" ;
group = "nogroup" ;
verb = 0 ;
rbl = [] ;
rhbl_client = [] ;
rhbl_rcpt = [] ;
rhbl_sender = [] ;
rhbl_helo = [] ;
dns_client = false;
dns_rev_client = false;
}
let to_bool s =
function
| "0" | "off" | "no" -> false
| "1" | "yes" | "on" -> true
| _ -> prerr_endline ("cannot read a boolean value for `"^s^"', possible values are on/off, yes/no, 1/0"); exit 1
let update_config cfg file =
let ic = open_in file in
try
while true do
let line = input_line ic in
if String.length line > 0 && line.[0] <> '#' then
match Str.split (Str.regexp "[ \t:]+") line with
| [] -> ()
| ["pidf"; f] -> cfg.pidf <- f
| ["sock"; s] -> cfg.sock <- Unix.ADDR_UNIX s
| ["sock"; ip; p] -> cfg.sock <- Unix.ADDR_INET (Unix.inet_addr_of_string ip, int_of_string p)
| ["user"; u] -> cfg.user <- u
| ["group"; g] -> cfg.group <- g
| ["verb"; "0"] -> cfg.verb <- 0
| ["verb"; "1"] -> cfg.verb <- 1
| ["rbl"; h] -> cfg.rbl <- h::cfg.rbl
| ["rhbl_client"; h] -> cfg.rhbl_client <- h::cfg.rhbl_client
| ["rhbl_helo"; h] -> cfg.rhbl_helo <- h::cfg.rhbl_helo
| ["rhbl_rcpt"; h] -> cfg.rhbl_rcpt <- h::cfg.rhbl_rcpt
| ["rhbl_sender"; h] -> cfg.rhbl_sender <- h::cfg.rhbl_sender
| ["dns_client"; d] -> cfg.dns_client <- to_bool "dns_client" d
| ["dns_rev_client"; e] -> cfg.dns_rev_client <- to_bool "dns_rev_client" e
(* deprecated settings *)
| ["rhbl"; h] -> prerr_endline "rhbl is deprecated, it defaults to rhbl_client which may not be what you want.";
cfg.rhbl_client <- h::cfg.rhbl_client
| _ -> prerr_string (Printf.sprintf "`%s' is not a valid config line\n" line)
done;
assert false
with End_of_file -> close_in ic; cfg
let read_config () =
let found_files = List.filter Sys.file_exists ["/etc/whitelister.conf" ; "whitelister.conf"] in
List.fold_left update_config (empty_config ()) found_files
(* Server thingies *)
open Server
let my_server_loop cfg sock =
pidfile_write ();
pidfile_close ();
log ("started, listening to " ^ (string_of_sockaddr cfg.sock));
while true do
let s = fst (unix_do Unix.accept sock) in
double_fork (fun s -> Unix.close sock; checker cfg s ; exit 0) s;
Unix.close s
done
(* Main LOOP *)
let _ =
let cfg = read_config () in
pidfile_open cfg.pidf;
pidfile_write ();
daemonize (cfg.user,cfg.group) (my_server_loop cfg) (bind_to_sock cfg.sock)