Minor fix for compatibility with Ruby versions that don't understand
[mailvisa.git] / mailvisad.rb
blob224c23ccddbb4d19416e0e82f377616558396e1e
1 require 'socket'
2 require 'wordlist'
3 require 'tokenize'
5 ### Defaults
7 confdir = ENV['HOME'] + '/settings/mailvisa'
8 sockpath = 'mailvisad.sock'
9 scorefile = 'scores'
10 pidfile = 'mailvisad.pid'
11 logfile = 'mailvisad.log'
13 ### Functions
15 def get_score word
16         score = $scores[word]
17         if score == nil
18                 0.4
19         else
20                 score
21         end
22 end
24 def most_interesting words
25         ## Sort words by extremity
26         interesting = words.sort do |x, y|
27                 (0.5 - get_score(y)).abs <=> (0.5 - get_score(x)).abs
28         end
29         # Return the most interesting words
30         interesting[0,20]
31 end
33 def spam_probability scores
34         prod = 1
35         scores.each { |x| prod = prod * x }
36         div = 1
37         scores.each { |x| div = div * (1 - x) }
38         prod / (prod + div)
39 end
41 usage = 'USAGE: ' + $0 + ' [options]'
43 help = <<EOT
44 Valid options are:
46 -c <path>       Look for files in <path> (default: $HOME/settings/mailvisa)
47 -f <path>       Load scores from <path> (default: scores)
48 -l <path>       Log to <path> (default: mailvisad.log)
49 -p <path>       Use <path> as pidfile (default: mailvisad.pid)
50 -s <path>       Use <path> as socket (default: mailvisad.sock)
51 EOT
53 ### Main program
55 ## Process command line
56 i = 0
57 while i < ARGV.length
58         case ARGV[i]
59         when '-h'
60                 puts usage
61                 print "\n" + help
62                 exit
63         when '-c'
64                 i = i + 1
65                 confdir = ARGV[i]
66         when '-s'
67                 i = i + 1
68                 sockpath = ARGV[i]
69         when '-f'
70                 i = i + 1
71                 scorefile = ARGV[i]
72         when '-l'
73                 i = i + 1
74                 logfile = ARGV[i]
75         when '-p'
76                 i = i + 1
77                 pidfile = ARGV[i]
78         else
79                 $stderr.puts 'Invalid option: ' + ARGV[i]
80                 $stderr.puts usage
81                 $stderr.puts 'Use "' + $0 + ' -h" for help'
82                 exit 0x80
83         end
84         i = i + 1
85 end
87 sockpath = confdir + '/' + sockpath if sockpath.index('/') == nil
88 scorefile = confdir + '/' + scorefile if scorefile.index('/') == nil
89 pidfile = confdir + '/' + pidfile if pidfile.index('/') == nil
90 logfile = confdir + '/' + logfile if logfile.index('/') == nil
92 ## Define procedure to load configuration
93 load_config = lambda do
94         db = load_wordlist open(scorefile)
95         $scores = db[:words]
96 end
98 # Set SIGHUP handler
99 trap('SIGHUP') { load_config.call }
101 # Open scorefile
102 scorefh = open scorefile
104 ## Open socket
105 begin
106         sock = UNIXServer.open sockpath
107 rescue Errno::EADDRINUSE
108         ## Figure out if the socket is actually live
109         begin
110                 sock = UNIXSocket.new sockpath
111                 # Aye, inform user and exit
112                 $stderr.puts 'mailvisad already bound to ' + sockpath
113                 exit
114         rescue
115                 # Nope, remove and try again
116                 if File.exists?(sockpath) && File.ftype(sockpath) == 'socket'
117                         File.unlink sockpath
118                         sock = UNIXServer.open sockpath
119                 end
120         end
123 # Open logfile
124 $stderr = open logfile, 'a'
125 $stdout = $stderr
127 # Fork child, exit parent
128 exit if fork    # Fork off a child, exit parent
130 # Set at_exit handler
131 at_exit {
132         sock.close
133         File.unlink sockpath if
134                 File.exists?(sockpath) && File.ftype(sockpath) == 'socket'
137 ## Detach from terminal
138 $stdin.close
139 pid = Process.setsid
141 ## Create pidfile
142 fh = open pidfile, 'w'
143 fh.puts pid
144 fh.close
146 # Load words
147 $scores = load_wordlist(scorefh)[:words]
149 while true
150         conn = sock.accept
152         begin
153                 message = conn.read 16384
154                 words = most_interesting tokenize(message)
155                 scores = words.map { |x| get_score x }
156                 probability = (spam_probability(scores) * 100).to_i / 100.0
158                 conn.puts probability
159         rescue
160         ensure
161                 conn.close
162         end