1
by Marco Balmer
Import upstream version 0.3.1 |
1 |
/* ---------------------------------------------------------------------------
|
2 |
* couriergrey - Greylisting filter for Courier
|
|
1.1.1
by Marco Balmer
Import upstream version 0.3.2 |
3 |
* Copyright (C) 2007-2012 Matthias Wimmer <m@tthias.eu>
|
1
by Marco Balmer
Import upstream version 0.3.1 |
4 |
*
|
5 |
* This program is free software; you can redistribute it and/or modify
|
|
6 |
* it under the terms of the GNU General Public License as published by
|
|
7 |
* the Free Software Foundation; either version 2 of the License, or
|
|
8 |
* (at your option) any later version.
|
|
9 |
*
|
|
10 |
* This program is distributed in the hope that it will be useful,
|
|
11 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13 |
* GNU General Public License for more details.
|
|
14 |
*
|
|
1.1.1
by Marco Balmer
Import upstream version 0.3.2 |
15 |
* You should have received a copy of the GNU Library General Public
|
16 |
* License along with this program; if not, write to the Free Software
|
|
17 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
|
18 |
* USA.
|
|
1
by Marco Balmer
Import upstream version 0.3.1 |
19 |
* ---------------------------------------------------------------------------
|
1.1.1
by Marco Balmer
Import upstream version 0.3.2 |
20 |
* vi: sw=4:tabstop=8
|
1
by Marco Balmer
Import upstream version 0.3.1 |
21 |
*/
|
22 |
||
23 |
#ifdef HAVE_CONFIG_H
|
|
24 |
# include <config.h>
|
|
25 |
#endif
|
|
26 |
||
27 |
#include "couriergrey.h" |
|
28 |
#include <cstring> |
|
29 |
#include <cerrno> |
|
30 |
#include <iostream> |
|
31 |
#include <unistd.h> |
|
32 |
#include <sys/types.h> |
|
33 |
#include <sys/socket.h> |
|
34 |
#include <arpa/inet.h> |
|
35 |
#include <sys/stat.h> |
|
36 |
#include <sys/un.h> |
|
37 |
#include <cstdio> |
|
38 |
#include <poll.h> |
|
39 |
#include <glibmm.h> |
|
40 |
#include <list> |
|
41 |
#include <syslog.h> |
|
42 |
#include <netinet/in.h> |
|
43 |
#include <popt.h> |
|
44 |
||
45 |
#define SOCKET_BACKLOG_SIZE 10
|
|
46 |
||
47 |
int main(int argc, char const** argv) { |
|
48 |
int do_version = 0; |
|
49 |
int dump_whitelist = 0; |
|
50 |
int dump_database = 0; |
|
51 |
int expire_database = 0; |
|
52 |
int ret = 0; |
|
53 |
char const* socket_location = LOCALSTATEDIR "/lib/courier/allfilters/couriergrey"; |
|
54 |
char const* whitelist_location = CONFIG_DIR "/whitelist_ip"; |
|
55 |
||
56 |
struct poptOption options[] = { |
|
57 |
{ "version", 'v', POPT_ARG_NONE, &do_version, 0, N_("print server version"), NULL}, |
|
58 |
{ "socket", 's', POPT_ARG_STRING, &socket_location, 0, N_("location of the filter domain socket"), "path"}, |
|
59 |
{ "whitelist", 'w', POPT_ARG_STRING, &whitelist_location, 0, N_("location of the whitelist file"), "path"}, |
|
60 |
{ "expire", 'e', POPT_ARG_INT, &expire_database, 0, N_("expire old database entries"), "days"}, |
|
61 |
{ "dumpwhitelist", 0, POPT_ARG_NONE, &dump_whitelist, 0, N_("dump the content of the parsed whitelist"), NULL}, |
|
62 |
{ "dumpdatabase", 0, POPT_ARG_NONE, &dump_database, 0, N_("dump the content of the greylisting database"), NULL}, |
|
63 |
POPT_AUTOHELP
|
|
64 |
POPT_TABLEEND
|
|
65 |
};
|
|
66 |
||
67 |
// Init multithreading
|
|
68 |
Glib::thread_init(); |
|
69 |
||
70 |
// Open Logging
|
|
71 |
::openlog(PACKAGE, LOG_PID, LOG_MAIL); |
|
72 |
||
73 |
// parse command line options
|
|
74 |
poptContext pCtx = poptGetContext(NULL, argc, argv, options, 0); |
|
75 |
while ((ret = poptGetNextOpt(pCtx)) >= 0) { |
|
76 |
switch (ret) { |
|
77 |
// access argument by poptGetOptArg(pCtx)
|
|
78 |
}
|
|
79 |
}
|
|
80 |
||
81 |
// error parsing command line?
|
|
82 |
if (ret < -1) { |
|
83 |
std::cout << poptBadOption(pCtx, POPT_BADOPTION_NOALIAS) << ": " << poptStrerror(ret) << std::endl; |
|
84 |
::closelog(); |
|
85 |
return 1; |
|
86 |
}
|
|
87 |
||
88 |
// anything left?
|
|
89 |
if (poptPeekArg(pCtx) != NULL) { |
|
90 |
// XXX i20n
|
|
91 |
std::cout << N_("Invalid argument: ") << poptGetArg(pCtx) << std::endl; |
|
92 |
::closelog(); |
|
93 |
return 1; |
|
94 |
}
|
|
95 |
||
96 |
// print version information?
|
|
97 |
if (do_version) { |
|
98 |
// XXX i20n
|
|
99 |
std::cout << PACKAGE << N_(" version ") << VERSION << std::endl << std::endl; |
|
100 |
std::cout << N_("Used filter socket is: ") << socket_location << std::endl; |
|
101 |
std::cout << N_("Used whitelist is: ") << whitelist_location << std::endl; |
|
102 |
std::cout << N_("Database is: ") << LOCALSTATEDIR "/cache/" PACKAGE "/deliveryattempts.gdbm" << std::endl; |
|
103 |
::closelog(); |
|
104 |
return 0; |
|
105 |
}
|
|
106 |
||
107 |
// read whitelist
|
|
108 |
couriergrey::whitelist used_whitelist(whitelist_location); |
|
109 |
||
110 |
// dump whitelist if requested
|
|
111 |
if (dump_whitelist) { |
|
112 |
used_whitelist.dump(); |
|
113 |
::closelog(); |
|
114 |
return 0; |
|
115 |
}
|
|
116 |
||
117 |
// expire database if requested
|
|
118 |
if (expire_database > 0) { |
|
119 |
try { |
|
120 |
couriergrey::timestore db; |
|
121 |
||
122 |
std::cout << N_("Expiring database entries older than ") << expire_database << N_(" days.") << std::endl; |
|
123 |
||
124 |
db.expire(expire_database); |
|
125 |
||
126 |
return 0; |
|
127 |
} catch (Glib::ustring msg) { |
|
128 |
std::cerr << msg << std::endl; |
|
129 |
return 1; |
|
130 |
}
|
|
131 |
}
|
|
132 |
||
133 |
// dump database if requested
|
|
134 |
if (dump_database) { |
|
135 |
try { |
|
136 |
couriergrey::timestore db; |
|
137 |
||
138 |
std::cout << N_("Content of the greylist database:") << std::endl; |
|
139 |
||
140 |
std::list<std::string> keys = db.get_keys(); |
|
141 |
for (std::list<std::string>::const_iterator p = keys.begin(); p != keys.end(); ++p) { |
|
142 |
std::cout << *p << std::endl; |
|
143 |
std::pair<std::time_t, std::time_t> times = db.fetch(*p); |
|
144 |
struct std::tm first_time_tm; |
|
145 |
gmtime_r(×.first, &first_time_tm); |
|
146 |
struct std::tm last_time_tm; |
|
147 |
gmtime_r(×.second, &last_time_tm); |
|
148 |
char first_time[128]; |
|
149 |
char last_time[128]; |
|
150 |
std::size_t first_time_size = strftime(first_time, sizeof(first_time), "%Y-%m-%dT%H:%M:%SZ", &first_time_tm); |
|
151 |
std::size_t last_time_size = strftime(last_time, sizeof(last_time), "%Y-%m-%dT%H:%M:%SZ", &last_time_tm); |
|
1.1.1
by Marco Balmer
Import upstream version 0.3.2 |
152 |
if (times.second - times.first >= 120) { |
153 |
std::cout << " A"; |
|
154 |
}
|
|
1
by Marco Balmer
Import upstream version 0.3.1 |
155 |
std::cout << "\t"; |
156 |
if (first_time_size > 0) { |
|
157 |
std::cout << first_time << " "; |
|
158 |
}
|
|
159 |
if (last_time_size > 0) { |
|
160 |
std::cout << last_time; |
|
161 |
}
|
|
162 |
std::cout << std::endl; |
|
163 |
}
|
|
164 |
return 0; |
|
165 |
} catch (Glib::ustring msg) { |
|
166 |
std::cerr << msg << std::endl; |
|
167 |
return 1; |
|
168 |
}
|
|
169 |
}
|
|
170 |
||
171 |
// open the domain socket
|
|
172 |
int domain_socket = -1; |
|
173 |
{
|
|
174 |
struct sockaddr_un addr; |
|
175 |
||
176 |
// calculate the temporary location where we create the socket
|
|
177 |
std::string temp_location = socket_location; |
|
178 |
std::string::size_type last_slash = temp_location.rfind("/"); |
|
179 |
if (last_slash == std::string::npos) { |
|
180 |
temp_location.insert(0, "."); |
|
181 |
} else { |
|
182 |
temp_location.insert(last_slash+1, "."); |
|
183 |
}
|
|
184 |
||
185 |
// check length of the socket location
|
|
186 |
if (temp_location.length() >= sizeof(addr.sun_path)) { |
|
187 |
std::cerr << N_("Socket name to long: ") << temp_location << std::endl; |
|
188 |
::closelog(); |
|
189 |
return 1; |
|
190 |
}
|
|
191 |
||
192 |
// unlink previously existing socket at the temp_location
|
|
193 |
ret = ::unlink(temp_location.c_str()); |
|
194 |
if (ret && errno != ENOENT) { |
|
195 |
std::cerr << N_("Problem creating domain socket at location ") << temp_location << ": " << std::strerror(errno) << std::endl; |
|
196 |
::closelog(); |
|
197 |
return 1; |
|
198 |
}
|
|
199 |
||
200 |
// create the domain socket
|
|
201 |
domain_socket = ::socket(PF_UNIX, SOCK_STREAM, 0); |
|
202 |
if (domain_socket == -1) { |
|
203 |
std::cerr << N_("Problem creating a unix domain socket: ") << std::strerror(errno) << std::endl; |
|
204 |
::closelog(); |
|
205 |
return 1; |
|
206 |
}
|
|
207 |
||
208 |
// if we opened the socket on fd#3 we have not been called as courierfilter
|
|
209 |
if (domain_socket == 3) { |
|
210 |
::close(domain_socket); |
|
211 |
std::cerr << N_("This file is not intended to be called directly.") << std::endl; |
|
212 |
::closelog(); |
|
213 |
return 1; |
|
214 |
}
|
|
215 |
||
216 |
// bind to the location
|
|
217 |
std::memset(&addr, 0, sizeof(addr)); |
|
218 |
addr.sun_family = AF_UNIX; |
|
219 |
std::strncpy(addr.sun_path, temp_location.c_str(), sizeof(addr.sun_path)-1); |
|
220 |
ret = ::bind(domain_socket, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)); |
|
221 |
if (ret) { |
|
222 |
std::cerr << N_("Could not bind to socket ") << temp_location << ": " << std::strerror(errno) << std::endl; |
|
223 |
::closelog(); |
|
224 |
return 1; |
|
225 |
}
|
|
226 |
||
227 |
// start listening on the socket
|
|
228 |
ret = ::listen(domain_socket, SOCKET_BACKLOG_SIZE); |
|
229 |
if (ret) { |
|
230 |
std::cerr << N_("Could not listen on socket ") << temp_location << ": " << std::strerror(errno) << std::endl; |
|
231 |
::closelog(); |
|
232 |
return 1; |
|
233 |
}
|
|
234 |
||
235 |
// move socket to final location
|
|
236 |
ret = std::rename(temp_location.c_str(), socket_location); |
|
237 |
if (ret) { |
|
238 |
std::cerr << N_("Cannot move socket to its operating location ") << socket_location << ": " << std::strerror(errno) << std::endl; |
|
239 |
::closelog(); |
|
240 |
return 1; |
|
241 |
}
|
|
242 |
}
|
|
243 |
||
244 |
// close fd #3 to signal that we are ready
|
|
245 |
::close(3); |
|
246 |
||
247 |
// log that we are up
|
|
248 |
::syslog(LOG_INFO, "%s started and ready", PACKAGE); |
|
249 |
||
250 |
// start waiting for something to happen
|
|
251 |
for (;;) { |
|
252 |
struct pollfd fds[2]; |
|
253 |
||
254 |
for (int c=0; c<2; c++) { |
|
255 |
std::memset(&fds[c], 0, sizeof(struct pollfd)); |
|
256 |
}
|
|
257 |
fds[0].fd = 0; |
|
258 |
fds[1].fd = domain_socket; |
|
259 |
fds[1].events = POLLIN; |
|
260 |
||
261 |
ret = ::poll(fds, 2, -1); |
|
262 |
if (ret < 0) { |
|
263 |
std::cerr << N_("Error waiting for I/O events: ") << std::strerror(errno) << std::endl; |
|
264 |
break; |
|
265 |
} else if (ret > 0) { |
|
266 |
if (fds[0].revents & POLLHUP) { |
|
267 |
// stdin closed, we have to shutdown
|
|
268 |
break; |
|
269 |
}
|
|
270 |
||
271 |
if (fds[1].revents & POLLIN) { |
|
272 |
// new connection, accept it
|
|
273 |
int accepted_connection = ::accept(domain_socket, NULL, 0); |
|
274 |
||
275 |
couriergrey::message_processor* processor = new couriergrey::message_processor(accepted_connection, used_whitelist); |
|
1.1.1
by Marco Balmer
Import upstream version 0.3.2 |
276 |
try { |
277 |
Glib::Thread::create(sigc::mem_fun(*processor, &couriergrey::message_processor::do_process), false); |
|
278 |
} catch (Glib::ThreadError const& te) { |
|
279 |
::syslog(LOG_INFO, "ThreadError caught in main thread: %s", te.what().c_str()); |
|
280 |
}
|
|
1
by Marco Balmer
Import upstream version 0.3.1 |
281 |
}
|
282 |
} else { |
|
283 |
std::clog << "XXX Returned without event ..." << std::endl; |
|
284 |
}
|
|
285 |
||
286 |
}
|
|
287 |
||
288 |
// cleanup
|
|
289 |
::close(domain_socket); |
|
290 |
::unlink(socket_location); |
|
291 |
||
292 |
// log that we are done
|
|
293 |
::syslog(LOG_INFO, "%s shut down", PACKAGE); |
|
294 |
||
295 |
// we're done
|
|
296 |
::closelog(); |
|
297 |
return 0; |
|
298 |
}
|