Added various log messages to mailvisad.
[mailvisa2.git] / src / mailvisa-check.ml
blobc5c891089bcab2e01f700d87dce067421d5a432c
1 (* mailvisa-check - Read a message, send it to mailvisad, and judge if it's ham or spam *)
3 open Sys
4 open Unix
6 open Common
8 (* Defaults *)
10 let default_confdir = (getenv "HOME") ^ "/settings/mailvisa2"
11 let default_sockpath = "mailvisad.sock"
12 let default_blocksize = 16384
13 let default_threshold = 0.5
14 let default_passthrough = true (** output message *)
15 let default_header = true (** output X-Spam headers *)
16 let default_errorcodes = false (** suppress error codes if true *)
17 let default_input = Pervasives.stdin
18 let default_output = Pervasives.stdout
20 (* Constants *)
22 let endl = "\x0d\x0a"
23 let spam_status = 160
25 let usage = "USAGE: mailvisa [options]"
26 let help = "Valid options are:
28 -c <path> Look for files in <path> (default: $HOME/settings/mailvisa)
29 -q Do not output message or X-Spam headers
30 -e Do not use exit status to indicate spam
31 -b <num> Read <num> bytes at a time (default: 16384)
32 -t <num> Threshold for flagging messages as spam (default: 0.5)
33 -m <command> Pipe output to <command>
34 -s <path> Use <path> to connect to mailvisad (default: mailvisad.sock)"
36 (* Functions *)
38 (** Parse command options from args into hash table. *)
39 let parse_options args =
40 let options = Hashtbl.create 8 in
41 let i = ref 1 in
42 let add_flag name = Hashtbl.add options name "true" in
43 let add_option name = increment i; Hashtbl.add options name args.(!i) in
44 while !i < (Array.length args) do
45 (match args.(!i) with
46 | "-h" ->
47 print_endline usage;
48 print_endline help;
49 exit 0
50 | "-b" -> add_option "blocksize"
51 | "-c" -> add_option "confdir"
52 | "-e" -> add_flag "errorcodes"
53 | "-m" -> add_option "mda"
54 | "-q" -> add_flag "quiet"
55 | "-t" -> add_option "threshold"
56 | "-s" -> add_option "sockpath"
57 | option ->
58 output_string Pervasives.stderr ("Invalid option: " ^ option ^ "\n");
59 exit 0x80);
60 increment i
61 done;
62 options
64 (* Initialization *)
66 let options = parse_options argv
67 let confdir = get_option_with_default options "confdir" default_confdir
69 (** Create an absolute path from a path, by prepending confdir if the path does not contain a slash. *)
70 let absolute_path path =
71 if String.contains path '/' then path
72 else (confdir ^ "/" ^ path)
74 (* Set constants from command line options *)
75 let sockpath = absolute_path (hash_get options "sockpath" ~default:default_sockpath)
76 let blocksize = hash_get_int options "blocksize" ~default:default_blocksize
77 let threshold = hash_get_float options "threshold" ~default:default_threshold
78 let quiet = hash_get_bool options "quiet" ~default:false
79 let passthrough = if quiet then false else default_passthrough
80 let header = if quiet then false else default_header
81 let errorcodes = hash_get_bool options "errorcodes" ~default:default_errorcodes
82 let input_channel = default_input
84 let output_channel =
85 (match hash_get_maybe options "mda" with
86 | None -> default_output
87 | Some command -> open_process_out command)
89 (* Main program *)
90 let _ =
91 let status = ref 0 in
92 let msg = String.create blocksize in
93 let bytes_read = input input_channel msg 0 blocksize in
94 (* Analyze message *)
95 begin
96 try
97 (* If we didn't get any bytes, raise End_of_file *)
98 if bytes_read = 0 then raise End_of_file;
99 (* We read n bytes; send them on to the daemon. *)
100 let conn =
101 try
102 unix_connect sockpath
103 with _ ->
104 failwith ("Cannot connect to " ^ sockpath)
106 let bytes_written = write conn msg 0 bytes_read in
107 ignore bytes_written;
108 shutdown conn SHUTDOWN_SEND;
109 (* Read response from daemon. *)
110 let response = input_line (in_channel_of_descr conn) in
111 let score = (float_of_string response) in
112 if score > threshold then status := spam_status
113 with error ->
114 status := 1;
115 match error with
116 | Failure msg ->
117 prerr_endline msg
118 | Unix_error (errno, funname, _) ->
119 prerr_endline (funname ^ ": " ^ (error_message errno))
120 | _ ->
121 prerr_endline (Printexc.to_string error)
122 end;
124 (* Output X-Spam: header if requested *)
125 if header && (!status = 0 || !status = spam_status) then
126 output_string output_channel ("X-Spam: " ^
127 (if !status = spam_status
128 then "true"
129 else "false") ^
130 endl);
132 (* Pass through message if requested *)
133 if passthrough then begin
134 let rec aux n =
135 if n > 0 then begin
136 output output_channel msg 0 n;
137 aux (input input_channel msg 0 blocksize)
139 in aux bytes_read
140 end;
142 (* Return status *)
143 exit (if errorcodes then 0 else !status)