move all code into /trunk
This commit is contained in:
commit
2aac3e1e88
7
AUTHORS
Normal file
7
AUTHORS
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
Main coder
|
||||||
|
|
||||||
|
Veit Wahlich <cru@zodia.de>
|
||||||
|
.spec file rewrite
|
||||||
|
patch for rejection if peer's ip doesn't resolve to a hostname
|
||||||
|
patch for stats transmission exactly every 60 minutes
|
335
ChangeLog
Normal file
335
ChangeLog
Normal file
|
@ -0,0 +1,335 @@
|
||||||
|
ChangeLog
|
||||||
|
---------
|
||||||
|
|
||||||
|
2007-07-20 20:03 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* 1.6 release
|
||||||
|
|
||||||
|
* Bugs:
|
||||||
|
|
||||||
|
* Fixed a DoS-causing, remotely explitable bug in Proxy.cpp. This bug only affects version 1.3 to
|
||||||
|
1.5, both inclusive. If you are using either 1.3, 1.4 or 1.5 UPDATE NOW.
|
||||||
|
Thanks to Veit Wahlich for finding and reporting the bug and for submitting
|
||||||
|
a preeliminar patch.
|
||||||
|
|
||||||
|
* While looking for similar vulnerabilities in the code, found a small
|
||||||
|
incorrection, although it doesn't have security implications.
|
||||||
|
|
||||||
|
|
||||||
|
2007-07-19 12:57 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* 1.5 release
|
||||||
|
|
||||||
|
* Enhancements:
|
||||||
|
|
||||||
|
* Allow permanently rejecting mails with dnsbl or spf errors. To use configure
|
||||||
|
return_temp_error_on_reject as false (default). To keep the old behaviour,
|
||||||
|
configure the above option as true.
|
||||||
|
|
||||||
|
* File logger can now be configured to only open the file sporadically to
|
||||||
|
write its buffer. This allows for external log rotators on platforms that
|
||||||
|
can't rename an open file (i.e. windows). The option is called
|
||||||
|
keep_file_locked. To use the new behaviour, configure as false, to keep the
|
||||||
|
old one configure as true.
|
||||||
|
|
||||||
|
* Implemented win32 service support. To enable, configure with
|
||||||
|
--enable-win32-service. The windows build on the website are already compiled
|
||||||
|
with this option. To install the service execute:
|
||||||
|
|
||||||
|
c:\hermes> hermes -install
|
||||||
|
|
||||||
|
To uninstall:
|
||||||
|
|
||||||
|
c:\hermes> hermes -uninstall
|
||||||
|
|
||||||
|
To start:
|
||||||
|
|
||||||
|
c:\hermes> net start hermes
|
||||||
|
|
||||||
|
To stop:
|
||||||
|
|
||||||
|
c:\hermes> net stop hermes
|
||||||
|
|
||||||
|
Of course, you can also use the service manager to start and stop the service.
|
||||||
|
Using the service code there's a big warning everyone should read:
|
||||||
|
|
||||||
|
The config file MUST be named "hermes.ini" and be located on the same
|
||||||
|
directory as "hermes.exe". Also, since hermes is started from another
|
||||||
|
directory, you have to specify the full path to the database:
|
||||||
|
|
||||||
|
database_file = "c:\hermes\greydatabase.db"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* Fixes:
|
||||||
|
|
||||||
|
* Fix SPF requests to be synchronized. I haven't seen a single failure from
|
||||||
|
this, but this is the right way.
|
||||||
|
|
||||||
|
* Removed an stale debug statement. It could be noticed when starting hermes
|
||||||
|
that the list of dns white/black lists was printed on the standard output.
|
||||||
|
|
||||||
|
* dns_{white,black}list_percentage now defaults to 100. Setting it to 0 makes
|
||||||
|
no sense and makes all your emails to be considered white/black listed.
|
||||||
|
|
||||||
|
* Fixed spec file to include the AUTHORS file.
|
||||||
|
|
||||||
|
* The value of spf_query now defaults to true when compiled with SPF support.
|
||||||
|
|
||||||
|
* Applied patch by Veit Wahlich that fixes stats submission to be each 60
|
||||||
|
minutes exactly. Previously it would send the stats on intervals of
|
||||||
|
approximattely 60 minutes.
|
||||||
|
|
||||||
|
* Whitelisting IPs is now partial like blacklisting. For example, whitelisting
|
||||||
|
192.168.0 will whitelist 192.168.0.* (192.168.0.0/24)
|
||||||
|
|
||||||
|
* Small fixes to the building system.
|
||||||
|
|
||||||
|
|
||||||
|
2007-06-14 20:23 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* 1.4 release
|
||||||
|
|
||||||
|
* Enhancements:
|
||||||
|
|
||||||
|
* SPF: if you enable query_spf, everytime someone gets through greylisting,
|
||||||
|
they will be checked for spf compliance. If they either FAIL or SOFTFAIL, they
|
||||||
|
will be rejected.
|
||||||
|
|
||||||
|
* Specify your hostname on the config file (option hostname). If it's empty,
|
||||||
|
it gets filled by gethostname() (as before)
|
||||||
|
|
||||||
|
* DNS Whitelisting: similar to DNS Blacklisting, but the other way around.
|
||||||
|
|
||||||
|
* DNS Whitelisting and DNS Blacklisting both support querying more than one
|
||||||
|
server at a time. It means that you don't have to rely 100% on a dns list, but
|
||||||
|
can use more than one. To control how many of the list need to list a server,
|
||||||
|
use dns_{black,white}list_percentage option on config file.
|
||||||
|
|
||||||
|
* If you define now whitelisted_disables_everything, whitelisted host will not
|
||||||
|
be forced to go through throttling and banner delaying (or anything else).
|
||||||
|
|
||||||
|
* Blacklisting is now partial. That means that if you blacklist 192.168.0. you
|
||||||
|
are actually blacklisting 192.168.0.* (192.168.0.0/24 if you prefer)
|
||||||
|
|
||||||
|
* Added the throttling_time option that controls how much we sleep between
|
||||||
|
lines when throttling a connection.
|
||||||
|
|
||||||
|
* Changed logging format. Should be clearer now, although there are still some
|
||||||
|
things I'd like to change.
|
||||||
|
|
||||||
|
* We are also logging now also when someone gets their connection dropped
|
||||||
|
because of throttling or data-before-banner (or black/whitelisting, spf, etc. ).
|
||||||
|
It should help to get a better feeling of how much spam we are stopping with
|
||||||
|
these techniques.
|
||||||
|
|
||||||
|
* We now can reject emails if peer doesn't have an inverse resolution (patch
|
||||||
|
by Veit Wahlich) or if the inverse resolution doesn't match the helo string.
|
||||||
|
Both of these features should be used with extreme care, and are disabled by
|
||||||
|
default. You shouldn't use them if you don't know what you are doing.
|
||||||
|
|
||||||
|
|
||||||
|
* Fixes:
|
||||||
|
|
||||||
|
* FileLogger.cpp: file logging now flushes its buffer after a few lines (15).
|
||||||
|
This should update the log on file more often.
|
||||||
|
|
||||||
|
* Configfile.tmpl: when compiling on windows, all default values should be
|
||||||
|
valid
|
||||||
|
|
||||||
|
* Fixed a bug when closing the filelogger file (most people noticed that
|
||||||
|
hermes crashed when closing when using file logger).
|
||||||
|
|
||||||
|
* Changed the X-Anti-Spam-Proxy header to be more clear.
|
||||||
|
|
||||||
|
* Fixed all typos with wether to whether
|
||||||
|
|
||||||
|
* Fixed a minor RFC-strict error when defining the non-existing extension
|
||||||
|
|
||||||
|
* Timezone _should_ be correct now on windows. If it isn't, write to the
|
||||||
|
mailing list with an example and why you think it's incorrect.
|
||||||
|
|
||||||
|
* Fixed configure.in. If you specify now --disable-openssl it will disable
|
||||||
|
openssl even if you have it installed
|
||||||
|
|
||||||
|
* Updated the .spec file (thanks again to Veit Wahlich's patch)
|
||||||
|
|
||||||
|
* Added AUTHORS file and also added lot's of docs to the windows release.
|
||||||
|
|
||||||
|
2007-05-18 20:11 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* 1.3 release
|
||||||
|
|
||||||
|
* added the add_headers option, will add the rfc-required "Received" headers
|
||||||
|
should give a better idea of where emails are coming/going
|
||||||
|
|
||||||
|
* also added date to logging when it is done to a file
|
||||||
|
|
||||||
|
* fixed filelogger, should now use file_logger_file config option
|
||||||
|
|
||||||
|
* windows version can now resolve addresses, so rbl works and also you can now
|
||||||
|
use fancy names like "localhost" instead of ugly ips like "127.0.0.1"
|
||||||
|
|
||||||
|
* updated rpm, hopefully everything should be ok now
|
||||||
|
|
||||||
|
2007-05-13 18:21 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* 1.2 release
|
||||||
|
|
||||||
|
* added rbl checking. Simply define rbl_domain in configfile
|
||||||
|
|
||||||
|
2007-04-20 12:04 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* Added an option to configure the initial delay of the SMTP banner
|
||||||
|
|
||||||
|
2007-04-19 20:28 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* Bugfix 1.1 release
|
||||||
|
|
||||||
|
* Implemented the bind_to config option. Defining bind_to in the configfile
|
||||||
|
will force hermes to only bind to one ip.
|
||||||
|
|
||||||
|
* Fixed a small bug when closing hermes with clean_db=false (it would segfault
|
||||||
|
previously)
|
||||||
|
|
||||||
|
* Added more documentation and updated http://www.hermes-project.com
|
||||||
|
|
||||||
|
2007-04-16 19:48 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* Initial 1.0 release
|
||||||
|
|
||||||
|
2007-04-09 20:27 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* *.{h,cpp}: add GPL license to all source files. also added gpl.txt with the
|
||||||
|
full license text on /docs
|
||||||
|
|
||||||
|
* Makefile.am: configure automake more correctly (not a lot, probably still
|
||||||
|
very wrong)
|
||||||
|
|
||||||
|
* TODO: cleaned up a bit
|
||||||
|
|
||||||
|
2007-04-09 18:57 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* *.{h,cpp}: Ifdef'd all output to terminal. From now on if you want all that
|
||||||
|
output, define REALLY_VERBOSE_DEBUG on config.h (once it is generated)
|
||||||
|
|
||||||
|
* generate_config.pl: generate also a default config file from the information
|
||||||
|
on Configfile.tmpl
|
||||||
|
|
||||||
|
2007-03-18 19:16 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* *Logger.{h,cpp}: Implement logging as a base class with different subclasses
|
||||||
|
depending on a configure option. Also added option to Configfile.tmpl to
|
||||||
|
configure the filename for FileLogger.
|
||||||
|
This change will allow us to port hermes more easily to other platforms,
|
||||||
|
specially non-unix(i.e. win32), but also will help if we don't have a logger
|
||||||
|
installed or if it's not compatible with the common interface (I'm using
|
||||||
|
metalog, btw).
|
||||||
|
|
||||||
|
2007-03-18 17:06 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* *: change all instances of spit to hermes to reflect project's new name
|
||||||
|
|
||||||
|
2007-03-09 18:19 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* Database.*: modified cleanDB, now the method counts the number of spams we
|
||||||
|
have blocked since the last time we cleaned
|
||||||
|
|
||||||
|
* spit.cpp: if we have configured it, send the number of spams blocked to a
|
||||||
|
server to keep the statistics
|
||||||
|
|
||||||
|
* Configfile.tmpl: added options to configure the previous changes.
|
||||||
|
submit_stats (bool) submit_stats_username (string) and submit_stats_password
|
||||||
|
(string)
|
||||||
|
|
||||||
|
2007-02-14 18:20 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* *.*: change all calls to Exception constructor to send also the file name and line
|
||||||
|
number
|
||||||
|
|
||||||
|
2007-02-12 19:03 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* Socket.*: new option setTimeout, sets the timeout for receive/send operations, should
|
||||||
|
help with the sockets getting blocked on recv() or send()
|
||||||
|
|
||||||
|
* Exception.*: new constructor accepts a filename and line number. The idea is to migrate
|
||||||
|
all calls to Exception to this new constructor so that errors get printed with their source
|
||||||
|
filename and line number to make debugging easier.
|
||||||
|
|
||||||
|
2007-02-10 17:25 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* Configfile.*: changed Configfile.{cpp,h} to be generated from Configfile.tmpl and
|
||||||
|
Configfile.{cpp,h}.in . It should be MUCH easier to add new config options
|
||||||
|
from now on. As a proof, adding options for the time to greylist and the
|
||||||
|
initial delay were a breeze compared to before.
|
||||||
|
|
||||||
|
* spit.cpp: instead of sending the data for thread_main in a pointer, send a
|
||||||
|
pointer to a stack and just pop the last element added.
|
||||||
|
|
||||||
|
2006-11-12 21:22 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* updated changelog to use gnu coding standards
|
||||||
|
|
||||||
|
* autotoolize spit
|
||||||
|
* Makefile.am
|
||||||
|
* configure.in
|
||||||
|
* Config.h: rename class to Configfile
|
||||||
|
|
||||||
|
2006-10-22 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* Socket.cpp: now creates the ssl context and loads certificates on the first socket
|
||||||
|
creation, so we now use less memory per-thread, AND we also load the certs
|
||||||
|
BEFORE chrooting, so now private_key and certificate DON'T need to be
|
||||||
|
(and are NOT recomended) INSIDE the chroot, which is a cool security feature.
|
||||||
|
|
||||||
|
2006-10-21 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* SQL.cpp: Changed SQL class so that every query is made through doQuery, that
|
||||||
|
controls that everything works the right way.
|
||||||
|
|
||||||
|
* Exception.cpp: When an Exception ocurrs, we notify it by email, either through smtp
|
||||||
|
or through sendmail
|
||||||
|
|
||||||
|
2006-10-15 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* Config.cpp: Fixed Config.cpp::validateConfig to take into account chrooting
|
||||||
|
|
||||||
|
* Socket.cpp: Fixed Socketp.cpp::close, we were sometimes closing fds twice
|
||||||
|
|
||||||
|
* main.cpp: if you send SIGINT or SIGTERM once you close gracefully, if you do
|
||||||
|
it twice, you forcefully stop the program, for when a socket is waiting to timeout,
|
||||||
|
and you can't restart the proxy in-between
|
||||||
|
|
||||||
|
2006-10-12 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* Config.cpp: Overhauled Config class
|
||||||
|
|
||||||
|
* main.cpp: fixed chrooting, now only /etc/resolv.conf is needed
|
||||||
|
|
||||||
|
2006-10-08 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* Socket.cpp: ssl is now fully working
|
||||||
|
decimal time for waiting in Socket::canRead
|
||||||
|
|
||||||
|
* SQL.cpp: whitelisting based on hostname of peer added.
|
||||||
|
|
||||||
|
* Logger.cpp: implements a logger for unix
|
||||||
|
|
||||||
|
* preeliminary port to solaris 10
|
||||||
|
|
||||||
|
2006-09-24 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* LOTS of bugfixes, some change in semantics and a bit of heavy-work
|
||||||
|
testing. Should be MUCH more stable now.
|
||||||
|
|
||||||
|
2006-09-18 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* main.cpp (main): Made threads detached to allow them to free resources
|
||||||
|
|
||||||
|
2006-09-17 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
|
||||||
|
* main.cpp (main): Create a thread to clean the database each hour
|
||||||
|
Threads now clean themselves up when finishing
|
||||||
|
|
||||||
|
2006-09-16 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
* Initial import to svn
|
3
Makefile.am
Normal file
3
Makefile.am
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
EXTRA_DIST = scripts/generate_config.pl ChangeLog TODO
|
||||||
|
|
||||||
|
SUBDIRS = src docs dists
|
21
TODO
Normal file
21
TODO
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
TODO:
|
||||||
|
-----
|
||||||
|
|
||||||
|
check ipv6 support, although I don't have any machine configured with ipv6..., but work is required anyway
|
||||||
|
|
||||||
|
windows Service
|
||||||
|
windowsishms
|
||||||
|
|
||||||
|
create documentation!!!!!!
|
||||||
|
add to documentation a note about the importance of correctly whitelisting peer hostname(you need a DOT before the name, so for example bongmail.com doesn't match against .gmail.com)
|
||||||
|
|
||||||
|
check return value from some functions
|
||||||
|
document some functions
|
||||||
|
|
||||||
|
check segfaults when receiving 1000+ threads
|
||||||
|
|
||||||
|
inet_ntoa/ntoa_inet are static!!!
|
||||||
|
|
||||||
|
reduce memory consumption per-thread
|
||||||
|
profile program
|
||||||
|
run through valgrind
|
3
bootstrap
Executable file
3
bootstrap
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
aclocal && autoconf && autoheader && automake --add-missing
|
134
configure.in
Normal file
134
configure.in
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
dnl Process this file with autoconf to produce a configure script.
|
||||||
|
|
||||||
|
AC_PREREQ(2.57)
|
||||||
|
|
||||||
|
AC_INIT([hermes], [1.7], [juanjo@gutierrezdequevedo.com])
|
||||||
|
|
||||||
|
dnl AC_CONFIG_AUX_DIR=([./config])
|
||||||
|
|
||||||
|
AM_CONFIG_HEADER([config.h])
|
||||||
|
|
||||||
|
AM_INIT_AUTOMAKE([1.7.8 foreign])
|
||||||
|
|
||||||
|
AC_PROG_CXX
|
||||||
|
AC_PROG_INSTALL
|
||||||
|
|
||||||
|
dnl
|
||||||
|
dnl check libraries and functions
|
||||||
|
dnl
|
||||||
|
|
||||||
|
AC_CHECK_FUNCS(getaddrinfo gai_strerror)
|
||||||
|
PKG_CHECK_MODULES(SQLite3, sqlite3, [], AC_MSG_ERROR("sqlite3 is required"))
|
||||||
|
PKG_CHECK_MODULES(OpenSSL, openssl, have_ssl=yes, have_ssl=no)
|
||||||
|
AC_CHECK_LIB(spf2,SPF_server_new, have_spf=yes, have_spf=no)
|
||||||
|
|
||||||
|
dnl
|
||||||
|
dnl end of libraries and functions
|
||||||
|
dnl
|
||||||
|
|
||||||
|
dnl
|
||||||
|
dnl check parameters
|
||||||
|
dnl
|
||||||
|
|
||||||
|
AC_ARG_WITH(logger-module,
|
||||||
|
[ --with-logger-module=module Module to log errors with. module is one of unix, file or null. default=unix],
|
||||||
|
[loggermodule=$withval],
|
||||||
|
[loggermodule=unix]
|
||||||
|
)
|
||||||
|
|
||||||
|
if test "$loggermodule" = unix; then
|
||||||
|
AC_DEFINE(LOGGER_CLASS,UnixLogger)
|
||||||
|
fi
|
||||||
|
if test "$loggermodule" = file; then
|
||||||
|
AC_DEFINE(LOGGER_CLASS,FileLogger)
|
||||||
|
fi
|
||||||
|
if test "$loggermodule" = null; then
|
||||||
|
AC_DEFINE(LOGGER_CLASS,NullLogger,[Define what logger we are using])
|
||||||
|
fi
|
||||||
|
|
||||||
|
AC_ARG_ENABLE(openssl,
|
||||||
|
[ --enable-openssl Enable OpenSSL support ],
|
||||||
|
[
|
||||||
|
if test x$enableval = xyes; then
|
||||||
|
if test x$have_ssl = xno; then
|
||||||
|
AC_MSG_ERROR("OpenSSL support requested but not detected")
|
||||||
|
fi
|
||||||
|
have_ssl=yes
|
||||||
|
else
|
||||||
|
have_ssl=no
|
||||||
|
fi
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
AC_ARG_ENABLE(spf,
|
||||||
|
[ --enable-spf Enable SPF support ],
|
||||||
|
[
|
||||||
|
if test x$enableval = xyes; then
|
||||||
|
if test x$have_spf = xno; then
|
||||||
|
AC_MSG_ERROR("SPF support requested but not detected")
|
||||||
|
fi
|
||||||
|
have_spf=yes
|
||||||
|
else
|
||||||
|
have_spf=no
|
||||||
|
fi
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
win32_service=no
|
||||||
|
AC_ARG_ENABLE(win32-service,
|
||||||
|
[ --enable-win32-service Enable win32 service support ],
|
||||||
|
[
|
||||||
|
if test x$enableval = xyes; then
|
||||||
|
win32_service=yes
|
||||||
|
fi
|
||||||
|
]
|
||||||
|
)
|
||||||
|
dnl
|
||||||
|
dnl end of parameters check
|
||||||
|
dnl
|
||||||
|
|
||||||
|
dnl
|
||||||
|
dnl config.h variables
|
||||||
|
dnl
|
||||||
|
|
||||||
|
if test x$have_ssl = xyes; then
|
||||||
|
AC_DEFINE(HAVE_SSL,1,[Defined if using openssl for SSL support])
|
||||||
|
fi
|
||||||
|
if test x$have_spf = xyes; then
|
||||||
|
AC_DEFINE(HAVE_SPF,1,[Defined if system has libspf2])
|
||||||
|
fi
|
||||||
|
if test x$win32_service = xyes; then
|
||||||
|
AC_DEFINE(WIN32_SERVICE,1,[Defined if we want to compile win32 service support])
|
||||||
|
fi
|
||||||
|
|
||||||
|
dnl
|
||||||
|
dnl end of config.h variables
|
||||||
|
dnl
|
||||||
|
|
||||||
|
dnl
|
||||||
|
dnl conditionals for makefiles
|
||||||
|
dnl
|
||||||
|
|
||||||
|
AM_CONDITIONAL(HAVE_SPF, test "$have_spf" = yes)
|
||||||
|
AM_CONDITIONAL(LOGGER_UNIX, test "$loggermodule" = unix)
|
||||||
|
AM_CONDITIONAL(LOGGER_NULL, test "$loggermodule" = null)
|
||||||
|
AM_CONDITIONAL(LOGGER_FILE, test "$loggermodule" = file)
|
||||||
|
AM_CONDITIONAL(WIN32_SERVICE, test "$win32_service" = yes)
|
||||||
|
|
||||||
|
dnl
|
||||||
|
dnl end of conditionals for makefiles
|
||||||
|
dnl
|
||||||
|
|
||||||
|
|
||||||
|
AC_CONFIG_FILES([Makefile src/Makefile docs/Makefile dists/Makefile dists/hermes.spec])
|
||||||
|
AC_OUTPUT
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
echo "Configuration for $PACKAGE_STRING"
|
||||||
|
echo
|
||||||
|
echo " SSL: $have_ssl"
|
||||||
|
echo " SPF: $have_spf"
|
||||||
|
echo " Logger: $loggermodule"
|
||||||
|
echo " Win32: $win32_service"
|
||||||
|
echo
|
2
dists/Makefile.am
Normal file
2
dists/Makefile.am
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
doc_DATA = hermesrc.example
|
||||||
|
EXTRA_DIST = fc_init hermes.spec hermes.spec.in
|
62
dists/fc_init
Executable file
62
dists/fc_init
Executable file
|
@ -0,0 +1,62 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Startup script for hermes
|
||||||
|
#
|
||||||
|
# chkconfig: 3 95 05
|
||||||
|
# description: hermes
|
||||||
|
|
||||||
|
# Source function library.
|
||||||
|
. /etc/rc.d/init.d/functions
|
||||||
|
|
||||||
|
prog=hermes
|
||||||
|
configfile=/etc/hermes/hermesrc
|
||||||
|
|
||||||
|
start() {
|
||||||
|
echo -n $"Starting $prog: "
|
||||||
|
daemon --check=$prog /usr/bin/hermes $configfile
|
||||||
|
RETVAL=$?
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
echo -n $"Stopping $prog: "
|
||||||
|
killproc $prog
|
||||||
|
RETVAL=$?
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
safestop() {
|
||||||
|
echo -n $"Stopping $prog(will process pending connections):"
|
||||||
|
killproc $prog -INT
|
||||||
|
rm /var/run/$prog.pid
|
||||||
|
RETVAL=$?
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
|
||||||
|
stop)
|
||||||
|
stop
|
||||||
|
;;
|
||||||
|
|
||||||
|
restart)
|
||||||
|
safestop
|
||||||
|
sleep 2
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
condrestart)
|
||||||
|
if test "x`pidfileofproc $prog`" != x; then
|
||||||
|
stop
|
||||||
|
start
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo $"Usage: $0 {start|stop|restart|condrestart}"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit $RETVAL
|
77
dists/hermes.spec.in
Normal file
77
dists/hermes.spec.in
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
Summary: An anti-spam SMTP proxy
|
||||||
|
Name: @PACKAGE@
|
||||||
|
Version: @VERSION@
|
||||||
|
Release: 0
|
||||||
|
License: GPL
|
||||||
|
Group: System Environment/Daemons
|
||||||
|
Packager: Veit Wahlich <cru@zodia.de>
|
||||||
|
URL: http://www.hermes-project.com/
|
||||||
|
Source0: http://www.hermes-project.com/files/%{name}-%{version}.tar.bz2
|
||||||
|
Buildroot: %{_tmppath}/%{name}-%{version}-%{release}-root
|
||||||
|
|
||||||
|
%description
|
||||||
|
hermes is a generic, lightweight, portable and fast anti-spam smtp proxy.
|
||||||
|
Supports greylisting, dns blacklisting/whitelisting, protocol throttling, banner delaying, spf and some
|
||||||
|
other tricks to reject most spam before it even enters your system.
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%setup -q
|
||||||
|
|
||||||
|
%build
|
||||||
|
%configure --docdir=%{_datadir}/doc/%{name}-%{version}
|
||||||
|
%__make %{?_smp_mflags}
|
||||||
|
|
||||||
|
%install
|
||||||
|
%__rm -rf %{buildroot}
|
||||||
|
%__make DESTDIR=%{buildroot} install
|
||||||
|
%__mkdir_p %{buildroot}%{_sysconfdir}/rc.d/init.d
|
||||||
|
%__mkdir_p %{buildroot}%{_sysconfdir}/hermes
|
||||||
|
%__mkdir_p %{buildroot}%{_localstatedir}/hermes
|
||||||
|
%__install -m 0755 dists/fc_init %{buildroot}%{_sysconfdir}/rc.d/init.d/hermes
|
||||||
|
%__install -m 0600 dists/hermesrc.example %{buildroot}%{_sysconfdir}/hermes/hermesrc
|
||||||
|
|
||||||
|
%clean
|
||||||
|
%__rm -rf %{buildroot}
|
||||||
|
|
||||||
|
%post
|
||||||
|
/sbin/chkconfig --add hermes
|
||||||
|
|
||||||
|
%preun
|
||||||
|
if [ $1 = 0 ]; then # execute this only if we are NOT doing an upgrade
|
||||||
|
%{_sysconfdir}/rc.d/init.d/hermes stop >/dev/null 2>&1
|
||||||
|
/sbin/chkconfig --del hermes
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
%files
|
||||||
|
%defattr(-, root, root, 0755)
|
||||||
|
%doc ChangeLog TODO AUTHORS dists/hermesrc.example docs/hermes-options.html docs/installing-hermes.txt docs/gpl.txt
|
||||||
|
%{_bindir}/hermes
|
||||||
|
%{_sysconfdir}/rc.d/init.d/hermes
|
||||||
|
%config %{_sysconfdir}/hermes/hermesrc
|
||||||
|
%dir %attr(0700,nobody,nobody) %{_localstatedir}/hermes
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
* Thu Jun 14 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com> 1.4
|
||||||
|
- removed patches, they are now on upstream
|
||||||
|
|
||||||
|
* Fri May 25 2007 Veit Wahlich <cru@zodia.de> 1.3-2
|
||||||
|
- added patch fix_whether (documentation fixes)
|
||||||
|
- added patch add_rejectnoresolve (reject on no DNS reverse resolution feature)
|
||||||
|
- changed RPM group to system daemon standard
|
||||||
|
|
||||||
|
* Sat May 19 2007 Veit Wahlich <cru@zodia.de> 1.3-1
|
||||||
|
- Made /etc/hermes/hermesrc readonly as it may contain passwords
|
||||||
|
- Fixed ownership and permissions of /var/hermes to match configuration default
|
||||||
|
- Silenced setup macro output as required by some distributions
|
||||||
|
- Fixed docdir to a LSB compliant location, will be replaced by rpmbuild
|
||||||
|
- Packaged extra documentation
|
||||||
|
- Removed hermes-options.html.in from docs
|
||||||
|
- Use directory macros for files section
|
||||||
|
- Further specfile cleanups and macro usage
|
||||||
|
|
||||||
|
* Tue May 15 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
- Fixed rpm to create /var/hermes
|
||||||
|
|
||||||
|
* Fri Apr 11 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
- Initial release
|
1252
docs/Doxyfile
Normal file
1252
docs/Doxyfile
Normal file
File diff suppressed because it is too large
Load diff
6
docs/Makefile.am
Normal file
6
docs/Makefile.am
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
EXTRA_DIST = Doxyfile gpl.txt installing-hermes.txt hermes-options.html hermes-options.html.in
|
||||||
|
|
||||||
|
doc_DATA = gpl.txt installing-hermes.txt hermes-options.html hermes-options.html.in
|
||||||
|
|
||||||
|
docs:
|
||||||
|
doxygen
|
339
docs/gpl.txt
Normal file
339
docs/gpl.txt
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
8
docs/hermes-options.html.in
Normal file
8
docs/hermes-options.html.in
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="hermesoption">
|
||||||
|
<a name="%name%"><h3>%name%</h3></a>
|
||||||
|
<div class="type">Type: %type%</div>
|
||||||
|
<div class="default">Default value: %default%</div>
|
||||||
|
<div class="explanation">
|
||||||
|
%explanation%
|
||||||
|
</div>
|
||||||
|
</div>
|
180
docs/installing-hermes.txt
Normal file
180
docs/installing-hermes.txt
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
Installing hermes
|
||||||
|
|
||||||
|
NOTE: this file has been generated from the source file
|
||||||
|
http://www.hermes-project.com/pages/installing-hermes
|
||||||
|
|
||||||
|
To install and configure a fully working hermes, you have to follow these
|
||||||
|
steps:
|
||||||
|
|
||||||
|
* Install_hermes
|
||||||
|
* Configure_hermes
|
||||||
|
* Change_your_current_SMTP_server_to_another_port
|
||||||
|
|
||||||
|
|
||||||
|
Install hermes
|
||||||
|
=================
|
||||||
|
|
||||||
|
If you are going to install from source, you first need to have sqlite3
|
||||||
|
installed with it's corresponding development package (usually sqlite3-devel or
|
||||||
|
sqlite3-dev). If you want to install hermes with SSL support, you also need
|
||||||
|
openssl with it's development headers (usually openssl-devel or openssl-dev).
|
||||||
|
|
||||||
|
- From source
|
||||||
|
|
||||||
|
If you have downloaded a .tar.gz or .tar.bz2 file, the procedure is as follows:
|
||||||
|
|
||||||
|
# tar xvfj hermes-1.0.tar.bz2 # extract file
|
||||||
|
# cd hermes-1.0 # change to the directory
|
||||||
|
# ./configure # execute configure
|
||||||
|
# make # compile
|
||||||
|
# make install # install
|
||||||
|
|
||||||
|
- From a source RPM
|
||||||
|
|
||||||
|
If you have downloaded a source rpm (.src.rpm) you have to compile it before
|
||||||
|
installing. To do so, you need the rpmbuild program.
|
||||||
|
|
||||||
|
# rpmbuild --rebuild hermes-1.0.src.rpm
|
||||||
|
|
||||||
|
After compiling, you should have a binary rpm on /usr/src/rpm/RPMS/i386 (or /
|
||||||
|
usr/src/redhat/RPMS/i386, depending on your distribution). With this file,
|
||||||
|
proceed to install the rpm.
|
||||||
|
|
||||||
|
- From a binary RPM
|
||||||
|
|
||||||
|
If you have downloaded a binary rpm (.rpm), installing should be pretty
|
||||||
|
straightforward:
|
||||||
|
|
||||||
|
# rpm -ihv hermes-1.0.rpm
|
||||||
|
|
||||||
|
Configure hermes
|
||||||
|
===================
|
||||||
|
|
||||||
|
This document will show you how to configure hermes in the most common way, but
|
||||||
|
if you want to read the full help for the hermes options, go to the full
|
||||||
|
reference_of_hermes_options.
|
||||||
|
|
||||||
|
- Config file
|
||||||
|
|
||||||
|
The first thing you need is a configuration file. There should be an example
|
||||||
|
file on /usr/local/share/doc/hermes or /usr/share/doc/hermes called
|
||||||
|
hermesrc.example. Copy this file to /etc/hermes/ (create the directory first if
|
||||||
|
needed)
|
||||||
|
|
||||||
|
# mkdir /etc/hermes
|
||||||
|
# cp /usr/share/doc/hermes/hermesrc.example /etc/hermes/hermesrc
|
||||||
|
|
||||||
|
Then edit the file, and let's start changing it:
|
||||||
|
|
||||||
|
# vi /etc/hermes/hermesrc
|
||||||
|
|
||||||
|
- Editing the config
|
||||||
|
|
||||||
|
In this section we will be creating a new file from scratch with only the
|
||||||
|
minimum required to get hermes working.
|
||||||
|
First, let's specify the user and group to drop privileges to:
|
||||||
|
|
||||||
|
user = nobody
|
||||||
|
group = nobody
|
||||||
|
|
||||||
|
Now let's configure where the greylisting database is saved (defaults to /var/
|
||||||
|
hermes/greylisting.db).
|
||||||
|
|
||||||
|
database_file = /var/hermes/greylisting.db
|
||||||
|
|
||||||
|
The only thing left is to specify the host and port with our real SMTP server:
|
||||||
|
|
||||||
|
server_host = localhost
|
||||||
|
server_port = 2525
|
||||||
|
|
||||||
|
After that, save the file and quit, and make sure that the database_file
|
||||||
|
directory exists and that it is owned by the user and group we specified
|
||||||
|
earlier
|
||||||
|
|
||||||
|
# mkdir /var/hermes
|
||||||
|
# chown nobody:nobody /var/hermes
|
||||||
|
|
||||||
|
If you have compiled hermes with SSL support, you have to configure the
|
||||||
|
certificate file and the private key
|
||||||
|
|
||||||
|
private_key_file = /etc/hermes/hermes.key
|
||||||
|
certificate_file = /etc/hermes/hermes.cert
|
||||||
|
|
||||||
|
Now we have to generate the key file and the certificate. To do this we will
|
||||||
|
use the openssl tool "openssl"
|
||||||
|
|
||||||
|
# openssl genrsa 1024 > /etc/hermes/hermes.key
|
||||||
|
Generating RSA private key, 1024 bit long modulus
|
||||||
|
...................................................++++++
|
||||||
|
.......++++++
|
||||||
|
e is 65537 (0x10001)
|
||||||
|
# openssl req -new -x509 -nodes -sha1 -days 365 -key /etc/hermes/hermes.key > /etc/hermes/hermes.cert
|
||||||
|
(at this point, openssl will ask lots of questions about your contact
|
||||||
|
information, organization, and the like. Once it's over, the certificate will
|
||||||
|
be generated)
|
||||||
|
|
||||||
|
Our resulting file looks like this:
|
||||||
|
|
||||||
|
user = nobody
|
||||||
|
group = nobody
|
||||||
|
database_file = /var/hermes/greylisting.db
|
||||||
|
server_host = localhost
|
||||||
|
server_port = 2525
|
||||||
|
private_key_file = /etc/hermes/hermes.key
|
||||||
|
certificate_file = /etc/hermes/hermes.cert
|
||||||
|
|
||||||
|
Change your server's port
|
||||||
|
============================
|
||||||
|
|
||||||
|
Changing the port of your SMTP server is a very different proccess depending on
|
||||||
|
your SMTP software, although they basically involve editing a file to change
|
||||||
|
the port number from 25 (default) to another port number. This document will
|
||||||
|
show you how to change the port number from 25 to 2525, which is hermes'
|
||||||
|
default. If your server's software is not listed here, try to search for
|
||||||
|
"<software-name> change default port" (i.e. "sendmail change default port") in
|
||||||
|
your favourite search engine.
|
||||||
|
|
||||||
|
- Sendmail
|
||||||
|
|
||||||
|
To change sendmail port, edit your sendmail.mc file (usually on /etc/mail) and
|
||||||
|
edit the line that says:
|
||||||
|
|
||||||
|
DAEMON_OPTIONS(`Port=smtp, Name=MTA')
|
||||||
|
|
||||||
|
and change the Port from smtp to 2525
|
||||||
|
|
||||||
|
DAEMON_OPTIONS(`Port=2525, Name=MTA')
|
||||||
|
|
||||||
|
After that type make to rebuild sendmail.cf
|
||||||
|
|
||||||
|
# make
|
||||||
|
|
||||||
|
And restart sendmail.
|
||||||
|
Of course, you can always edit the sendmail.cf directly, but if you know how/
|
||||||
|
what to change, then you don't need this help.
|
||||||
|
|
||||||
|
- Postfix
|
||||||
|
|
||||||
|
If you are using postfix, edit /etc/postfix/master.cf and change the line that
|
||||||
|
reads
|
||||||
|
|
||||||
|
smtp inet n - n - - smtpd
|
||||||
|
|
||||||
|
to read
|
||||||
|
|
||||||
|
2525 inet n - n - - smtpd
|
||||||
|
|
||||||
|
After that, restart postfix.
|
||||||
|
|
||||||
|
- Qmail
|
||||||
|
|
||||||
|
The easiest way to configure qmail's listening port is to edit /etc/services
|
||||||
|
and change the line that says
|
||||||
|
|
||||||
|
smtp 25/tcp mail
|
||||||
|
|
||||||
|
to
|
||||||
|
|
||||||
|
smtp 2525/tcp mail
|
||||||
|
|
||||||
|
and restart qmail.
|
123
scripts/generate_config.pl
Executable file
123
scripts/generate_config.pl
Executable file
|
@ -0,0 +1,123 @@
|
||||||
|
#!/usr/bin/perl -w
|
||||||
|
|
||||||
|
# this small script generates the Configfile class from the
|
||||||
|
# Configfile.cpp.in and Configfile.h.in. this way when we want
|
||||||
|
# to add a new option to the config file, we just have to put it
|
||||||
|
# on Configfile.tmpl and automagically it will appear on our code
|
||||||
|
# It will also generate an example hermesrc from the same info.
|
||||||
|
# 2007-04-17 Now it also generates an html document for our webpage
|
||||||
|
|
||||||
|
my $hvar="";
|
||||||
|
my $cppvar1="",$cppvar2="",$cppvar3="",$conf_example="",$htmlvar="";
|
||||||
|
|
||||||
|
open HTMLIN, "<../docs/hermes-options.html.in";
|
||||||
|
$htmltempl=join("",<HTMLIN>);
|
||||||
|
close HTMLIN;
|
||||||
|
|
||||||
|
while(<>)
|
||||||
|
{
|
||||||
|
chomp;
|
||||||
|
if(! /^#/ && ! /^\t*$/ && ! /^\*/)
|
||||||
|
{
|
||||||
|
s/^\s+//;s/\s+$//;
|
||||||
|
@_=split ",";
|
||||||
|
my $camelcased=&camel_case($_[1]);
|
||||||
|
my $type=$_[0];
|
||||||
|
$type="list<string>" if($type =~ /list/);
|
||||||
|
$hvar1.="$type $_[1];\n";
|
||||||
|
$hvar2.="$type& get$camelcased();\n";
|
||||||
|
if($type =~ /list/)
|
||||||
|
{
|
||||||
|
$cppvar1.="$_[1]=Configfile::parseAsList($_[2]);\n";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$cppvar1.="$_[1]=$_[2];\n";
|
||||||
|
}
|
||||||
|
$cppvar2.="PARSE_".uc($_[0])."(\"$_[1]\",$_[1])\n";
|
||||||
|
$cppvar3.="GET_VAR(get$camelcased,$_[1],$type&)\n";
|
||||||
|
$conf_example.="$_[1] = $_[2]\n\n";
|
||||||
|
my $htmltemp=$htmltempl;
|
||||||
|
$htmltemp =~ s/%type%/$_[0]/;
|
||||||
|
$htmltemp =~ s/%name%/$_[1]/g;
|
||||||
|
$htmltemp =~ s/%default%/$_[2]/;
|
||||||
|
$htmltemp =~ s/%explanation%/$htmlexpl/;
|
||||||
|
$htmlexpl="";
|
||||||
|
$htmlvar.=$htmltemp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(/^\*clean\*$/) # clean restarts our htmlexpl contents
|
||||||
|
{
|
||||||
|
$htmlexpl="";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(/^\*/)
|
||||||
|
{
|
||||||
|
s/^\*$//;
|
||||||
|
s/^\*/#/;
|
||||||
|
$conf_example.="$_\n";
|
||||||
|
chomp;
|
||||||
|
s/^#//;
|
||||||
|
s/>/>/;
|
||||||
|
$htmlexpl.="$_\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chomp $cppvar1;
|
||||||
|
chomp $cppvar2;
|
||||||
|
chomp $cppvar3;
|
||||||
|
chomp $hvar1;
|
||||||
|
chomp $hvar2;
|
||||||
|
chomp $conf_example;
|
||||||
|
|
||||||
|
open CPPIN, "<Configfile.cpp.in";
|
||||||
|
$cppstr=join("",<CPPIN>);
|
||||||
|
close CPPIN;
|
||||||
|
open CPPOUT, ">Configfile.cpp";
|
||||||
|
$cppstr =~ s/%templ_default_values%/$cppvar1/;
|
||||||
|
$cppstr =~ s/%templ_parsevars%/$cppvar2/;
|
||||||
|
$cppstr =~ s/%templ_getmethods%/$cppvar3/;
|
||||||
|
print CPPOUT $cppstr;
|
||||||
|
close CPPOUT;
|
||||||
|
|
||||||
|
open HIN, "<Configfile.h.in";
|
||||||
|
$hstr=join("",<HIN>);
|
||||||
|
close HIN;
|
||||||
|
open HOUT, ">Configfile.h";
|
||||||
|
$hstr =~ s/%templ_privateattribs%/$hvar1/;
|
||||||
|
$hstr =~ s/%templ_publicmethods%/$hvar2/;
|
||||||
|
print HOUT $hstr;
|
||||||
|
close HOUT;
|
||||||
|
|
||||||
|
open RCEX, ">../dists/hermesrc.example";
|
||||||
|
print RCEX $conf_example;
|
||||||
|
close RCEX;
|
||||||
|
|
||||||
|
open HTML, ">../docs/hermes-options.html";
|
||||||
|
print HTML $htmlvar;
|
||||||
|
close HTML;
|
||||||
|
|
||||||
|
sub camel_case()
|
||||||
|
{
|
||||||
|
my $str=shift;
|
||||||
|
my $outstr="";
|
||||||
|
|
||||||
|
for($i=0;$i<length($str);$i++)
|
||||||
|
{
|
||||||
|
my $letter=substr($str,$i,1);
|
||||||
|
if($letter eq "_")
|
||||||
|
{
|
||||||
|
$i++;
|
||||||
|
$outstr.=uc(substr($str,$i,1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$outstr.=$letter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ucfirst($outstr);
|
||||||
|
}
|
172
src/Configfile.cpp.in
Normal file
172
src/Configfile.cpp.in
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include "Configfile.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default config
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
Configfile::Configfile()
|
||||||
|
{
|
||||||
|
%templ_default_values%
|
||||||
|
}
|
||||||
|
|
||||||
|
void Configfile::parse(string file)
|
||||||
|
{
|
||||||
|
ifstream f;
|
||||||
|
char line[255];
|
||||||
|
int equalpos;
|
||||||
|
|
||||||
|
f.open(file.c_str(),ios::in);
|
||||||
|
while(!f.eof())
|
||||||
|
{
|
||||||
|
f.getline(line,255);
|
||||||
|
string l=Utils::trim(line);
|
||||||
|
if('#'!=l[0]&&l!=""&&l.find("="))
|
||||||
|
{
|
||||||
|
equalpos=l.find("=");
|
||||||
|
string option=Utils::trim(l.substr(0,equalpos));
|
||||||
|
string value=Utils::trim(l.substr(equalpos+1));
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << l << endl;
|
||||||
|
cout << option << "->" << value << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
//this is a bit of a hack, but simplifies a lot this function
|
||||||
|
#define PARSE_INT(x,y) if(x==option) y=Configfile::parseAsInt(value); else
|
||||||
|
#define PARSE_BOOL(x,y) if(x==option) y=Configfile::parseAsBool(value); else
|
||||||
|
#define PARSE_STRING(x,y) if(x==option) y=Configfile::parseAsString(value); else
|
||||||
|
#define PARSE_LIST(x,y) if(x==option) y=Configfile::parseAsList(value); else
|
||||||
|
|
||||||
|
%templ_parsevars%
|
||||||
|
{
|
||||||
|
throw Exception("Option \""+option+"\" with value \""+value+"\" is not recognized",__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
#undef PARSE_INT
|
||||||
|
#undef PARSE_BOOL
|
||||||
|
#undef PARSE_STRING
|
||||||
|
#undef PARSE_LIST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifndef WIN32
|
||||||
|
uid=Utils::usertouid(user);
|
||||||
|
gid=Utils::grouptogid(group);
|
||||||
|
#endif //WIN32
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
//again, this is a BIG HACK, but it simplifies code a lot
|
||||||
|
#define GET_VAR(x,y,z) z Configfile::x(){ return y;}
|
||||||
|
|
||||||
|
GET_VAR(getUid,uid,int)
|
||||||
|
GET_VAR(getGid,gid,int)
|
||||||
|
%templ_getmethods%
|
||||||
|
|
||||||
|
#undef GET_VAR
|
||||||
|
|
||||||
|
void Configfile::validateConfig()
|
||||||
|
{
|
||||||
|
#ifndef WIN32
|
||||||
|
//check if we are root if we want to bind to a port lower than 1024
|
||||||
|
if(getuid()!=0&&listening_port<1024)
|
||||||
|
throw Exception(_("You can't bind to a port lower than 1024 without being root"),__FILE__,__LINE__);
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
//check if ssl is usable
|
||||||
|
if(!Utils::file_exists(certificate_file))
|
||||||
|
throw Exception("Certificate file "+certificate_file+" doesn't exist.\nTo generate a certificate look in hermesrc.example, there is an example there.",__FILE__,__LINE__);
|
||||||
|
|
||||||
|
if(!Utils::file_exists(private_key_file))
|
||||||
|
throw Exception("Private key file "+private_key_file+" doesn't exist.\nTo generate a private key look in hermesrc.example, there is an example there.",__FILE__,__LINE__);
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
//check if chroot dir exist //TODO: check that files needed in chroot exist
|
||||||
|
//for now only /etc/resolv.conf, but we're working on it :-D
|
||||||
|
if(""!=chroot&&!Utils::dir_exists(chroot))
|
||||||
|
throw Exception("Directory "+chroot+" doesn't exist, can't chroot to it.",__FILE__,__LINE__);
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
//check if we have submit_stats on but no user and password
|
||||||
|
if(getSubmitStats()&&(""==getSubmitStatsUsername()||""==getSubmitStatsPassword()))
|
||||||
|
throw Exception("You have configured hermes to send stats, but have not configured a username or password.\n"
|
||||||
|
"If you don't have one, go to http://www.hermes-project.com and register there",__FILE__,__LINE__);
|
||||||
|
|
||||||
|
#ifndef HAVE_SSL
|
||||||
|
//check if we have activated submit_stats_ssl not having ssl activated
|
||||||
|
if(getSubmitStatsSsl())
|
||||||
|
throw Exception("You have configured stats submission through SSL, but hermes was compiled without SSL support",__FILE__,__LINE__);
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
string Configfile::parseAsString(string str)
|
||||||
|
{
|
||||||
|
//remove "" round the string
|
||||||
|
if('"'==str[0])
|
||||||
|
str=str.substr(1);
|
||||||
|
if('"'==str[str.length()-1])
|
||||||
|
str=str.substr(0,str.length()-1);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Configfile::parseAsBool(string str)
|
||||||
|
{
|
||||||
|
if("yes"==str||"on"==str||"1"==str||"true"==str)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long int Configfile::parseAsInt(string str)
|
||||||
|
{
|
||||||
|
long int value;
|
||||||
|
|
||||||
|
errno=0; //to know why we do this, read NOTES on strtol(3)
|
||||||
|
value=strtol(str.c_str(),NULL,10);
|
||||||
|
if(errno)
|
||||||
|
throw Exception("Error parsing as int ("+Utils::errnotostrerror(errno)+")",__FILE__,__LINE__);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
list<string> Configfile::parseAsList(string str)
|
||||||
|
{
|
||||||
|
list<string> tmpList;
|
||||||
|
string::size_type startpos=0,endpos=0,len;
|
||||||
|
string tmpstr;
|
||||||
|
|
||||||
|
str=Configfile::parseAsString(str); //remove quotes around string
|
||||||
|
|
||||||
|
len=str.length();
|
||||||
|
while(startpos<len&&string::npos!=endpos)
|
||||||
|
{
|
||||||
|
endpos=str.find(',',startpos);
|
||||||
|
if(string::npos==endpos)
|
||||||
|
tmpstr=str.substr(startpos);
|
||||||
|
else
|
||||||
|
tmpstr=str.substr(startpos,endpos-startpos);
|
||||||
|
tmpList.push_back(Utils::trim(tmpstr));
|
||||||
|
startpos=endpos+1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpList;
|
||||||
|
}
|
52
src/Configfile.h.in
Normal file
52
src/Configfile.h.in
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef CONFIGFILE_H
|
||||||
|
#define CONFIGFILE_H
|
||||||
|
|
||||||
|
#include "hermes.h"
|
||||||
|
#include <list>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class Configfile
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static string parseAsString(string);
|
||||||
|
static bool parseAsBool(string);
|
||||||
|
static long parseAsInt(string);
|
||||||
|
static list<string> parseAsList(string);
|
||||||
|
int uid;
|
||||||
|
int gid;
|
||||||
|
%templ_privateattribs%
|
||||||
|
public:
|
||||||
|
Configfile();
|
||||||
|
void parse(string);
|
||||||
|
void validateConfig();
|
||||||
|
int getUid();
|
||||||
|
int getGid();
|
||||||
|
%templ_publicmethods%
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //CONFIGFILE_H
|
253
src/Configfile.tmpl
Normal file
253
src/Configfile.tmpl
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
#include "../config.h"
|
||||||
|
|
||||||
|
* comments MUST begin with a #.
|
||||||
|
*
|
||||||
|
* boolean options recognise (true, 1, on, yes) as true
|
||||||
|
* and anything else as false
|
||||||
|
*
|
||||||
|
* string literals can be surrounded by the " character,
|
||||||
|
* but everything else CANNOT be
|
||||||
|
*
|
||||||
|
|
||||||
|
*clean*
|
||||||
|
#ifndef WIN32
|
||||||
|
|
||||||
|
* whether to fork to the background. initscripts require
|
||||||
|
* this to be true most of the time.
|
||||||
|
bool,background,true
|
||||||
|
|
||||||
|
* chroot to this directory on startup.
|
||||||
|
* this path is ABSOLUTE, it WON'T work with a relative path,
|
||||||
|
* because we are chrooting to the dir BEFORE chrooting, as a
|
||||||
|
* security measure.
|
||||||
|
* to disable chrooting, use an empty string (default).
|
||||||
|
string,chroot,""
|
||||||
|
|
||||||
|
* drop privileges once running? recomended.
|
||||||
|
bool,drop_privileges,true
|
||||||
|
|
||||||
|
* user to drop privileges to.
|
||||||
|
string,user,"nobody"
|
||||||
|
|
||||||
|
* group to drop privileges to.
|
||||||
|
string,group,"nobody"
|
||||||
|
|
||||||
|
* write a pid file with the pid of the main hermes server.
|
||||||
|
* if you set background=true above, this will write the pid
|
||||||
|
* of the forked hermes, not the original.
|
||||||
|
string,pid_file,"/var/run/hermes.pid"
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
* the port where hermes will listen for new connection.
|
||||||
|
* if you are going to use a port lower than 1024 (almost always,
|
||||||
|
* smtp is 25, smtps is 465 and delivery is 587), then you need
|
||||||
|
* to run as root (you can drop privileges) or with setUID active.
|
||||||
|
int,listening_port,25
|
||||||
|
|
||||||
|
* the ip to bind to. if you leave it empty (default), then it
|
||||||
|
* listens on all available ips
|
||||||
|
string,bind_to,""
|
||||||
|
|
||||||
|
* the host of the real smtp server.
|
||||||
|
* if your server is qmail and you have the AUTH patch,
|
||||||
|
* DON'T use localhost, use the external IP instead.
|
||||||
|
string,server_host,"localhost"
|
||||||
|
|
||||||
|
* the port for the real smtp server.
|
||||||
|
int,server_port,2525
|
||||||
|
|
||||||
|
* database file to use.
|
||||||
|
* if you are chrooting, the path is relative to the chroot:
|
||||||
|
* real filepath = chroot + database_file
|
||||||
|
#ifdef WIN32
|
||||||
|
string,database_file,"greylisting.db"
|
||||||
|
#else
|
||||||
|
string,database_file,"/var/hermes/greylisting.db"
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
* whether to use greylisting.
|
||||||
|
* greylisting will slightly delay your emails (configurable, see below)
|
||||||
|
* to stop most spam. is the most efective technique in use by hermes.
|
||||||
|
bool,greylist,true
|
||||||
|
|
||||||
|
* whether to throttle connection.
|
||||||
|
* it will force some spammers (the more impatient ones) to drop the connection
|
||||||
|
* and leave you alone.
|
||||||
|
bool,throttle,true
|
||||||
|
|
||||||
|
* throttling time
|
||||||
|
* this is the time (in seconds) that hermes will wait between each sent line.
|
||||||
|
* don't set this too high (more than 3), as that will drop MANY connections
|
||||||
|
int,throttling_time,1
|
||||||
|
|
||||||
|
* whether we should check if there is data before we send the SMTP banner.
|
||||||
|
* if there is data the email is almost certainly spam.
|
||||||
|
bool,allow_data_before_banner,false
|
||||||
|
|
||||||
|
* dns blacklist domain list to check.
|
||||||
|
* if this is empty (default) hermes will not check anything, effectively disabling
|
||||||
|
* dns blacklisting.
|
||||||
|
* recommended value is "zen.spamhaus.org"
|
||||||
|
list,dns_blacklist_domains,""
|
||||||
|
|
||||||
|
* percentage of domains that have to blacklist an ip before considering it blacklisted.
|
||||||
|
* for example if you need a domain to be listed in only half of the blacklists to be considered
|
||||||
|
* as listed, just define dns_blacklist_percentage as 50 (50%)
|
||||||
|
int,dns_blacklist_percentage,100
|
||||||
|
|
||||||
|
* dns whitelist domain to check.
|
||||||
|
* if this is empty (default) hermes will not check anything, effectively disabling
|
||||||
|
* dns whitelisting.
|
||||||
|
* this lists should only list hosts that have a history of NOT sending spam.
|
||||||
|
* recommended value is "list.dnswl.org"
|
||||||
|
list,dns_whitelist_domains,""
|
||||||
|
|
||||||
|
* percentage of domains that have to whitelist an ip before considering it whitelisted.
|
||||||
|
* for example if you need a domain to be listed in only half of the whitelists to be considered
|
||||||
|
* as listed, just define dns_whitelist_percentage as 50 (50%).
|
||||||
|
int,dns_whitelist_percentage,100
|
||||||
|
|
||||||
|
* time to delay the initial SMTP banner
|
||||||
|
int,banner_delay_time,5
|
||||||
|
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
* email to notify exceptions to.
|
||||||
|
* CAVEAT: the code that does this is VERY BUGGY and VERY VERBOSE, don't use unless you
|
||||||
|
* are a developer looking for a bug.
|
||||||
|
string,notify_to,""
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
|
||||||
|
* greylisting options.
|
||||||
|
*
|
||||||
|
*clean*
|
||||||
|
|
||||||
|
* initial expiry time.
|
||||||
|
* when email is first recorded, it will expire after this time (in minutes).
|
||||||
|
int,initial_expiry,240
|
||||||
|
|
||||||
|
* initial period of time (in minutes) during which a retry on the spammer's side will FAIL.
|
||||||
|
int,initial_blacklist,5
|
||||||
|
|
||||||
|
* once we have whitelisted a triplet, how long it stays whitelisted (in days).
|
||||||
|
* 36 is a magic number, is the maximum days between a day and the same day next month
|
||||||
|
int,whitelist_expiry,36
|
||||||
|
|
||||||
|
* whether to submit stats.
|
||||||
|
bool,submit_stats,true
|
||||||
|
|
||||||
|
* should stats be submited using SSL?
|
||||||
|
* recomended, but some people will compile without ssl.
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
bool,submit_stats_ssl,true
|
||||||
|
#else
|
||||||
|
bool,submit_stats_ssl,false
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
|
* username (used to submit stats).
|
||||||
|
* you can register on http://www.hermes-project.com
|
||||||
|
string,submit_stats_username,"anonymous"
|
||||||
|
|
||||||
|
* password
|
||||||
|
string,submit_stats_password,"anonymous"
|
||||||
|
|
||||||
|
#if LOGGER_CLASS==FileLogger
|
||||||
|
* if you are using the filelogger, which file to log to.
|
||||||
|
string,file_logger_filename,"hermes.log"
|
||||||
|
|
||||||
|
* whether to keep the logger file locked between writes
|
||||||
|
bool,keep_file_locked,true
|
||||||
|
|
||||||
|
* frequency for log rotating in minutes
|
||||||
|
* default is 1440 (1 day)
|
||||||
|
* 0 means no rotation
|
||||||
|
int,log_rotation_frequency,1440
|
||||||
|
|
||||||
|
* format for the logfile rotation
|
||||||
|
* if you are using logfile rotation, file_logger represents the filename
|
||||||
|
* to which the logger will write, while this is the name files will get
|
||||||
|
* when rotated
|
||||||
|
* you can use the following variables:
|
||||||
|
* %%year%% - current year (4 digits)
|
||||||
|
* %%month%% - current month
|
||||||
|
* %%day%% - current day
|
||||||
|
* %%hour%% - current hour
|
||||||
|
* %%minute%% - current minute
|
||||||
|
* all of them are zero-padded
|
||||||
|
string,rotate_filename,"hermes-%%year%%-%%month%%-%%day%%-%%hour%%:%%minute%%.log"
|
||||||
|
#endif //LOGGER_CLASS==FileLogger
|
||||||
|
|
||||||
|
* whether to clean the database file and send stats.
|
||||||
|
* if you have two instances of hermes running (for example one for smtp and other for smtps)
|
||||||
|
* you want to configure all of them but one to use clean_db=false.
|
||||||
|
* also, you might prefer to not clean the database at all for many reasons (for example to
|
||||||
|
* keep a huge file around with all your system's email data).
|
||||||
|
* anyway, this doesn't mean in anyway that entries in the database won't expire, only that they
|
||||||
|
* will be left hanging around without any use.
|
||||||
|
bool,clean_db,true
|
||||||
|
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
* ssl-related config options
|
||||||
|
* NOTE: this NEEDS the openssl library
|
||||||
|
*
|
||||||
|
*clean*
|
||||||
|
|
||||||
|
* should we try to connect to our real smtp server using ssl?
|
||||||
|
* not really neccesary unless real smtp server is on other machine.
|
||||||
|
bool,outgoing_ssl,false
|
||||||
|
|
||||||
|
* should we accept connections using ssl?
|
||||||
|
* NOTE: this does NOT disable the starttls capability, only starts hermes expecting SSL negotiation.
|
||||||
|
* starttls is handled the following way: if you have ssl, it is always on. clients can request it at
|
||||||
|
* any time an hermes will change to ssl at once. if you don't have ssl, hermes will refuse to starttls
|
||||||
|
* with a 354 error code, although it WILL still accept the command. connection should proceed normally
|
||||||
|
* even on that event
|
||||||
|
bool,incoming_ssl,false
|
||||||
|
|
||||||
|
* file with our private key (PEM format).
|
||||||
|
* to generate, execute:
|
||||||
|
* # openssl genrsa 1024 > private.key
|
||||||
|
string,private_key_file,"/etc/hermes/hermes.key"
|
||||||
|
|
||||||
|
* file with our server certificate (PEM format).
|
||||||
|
* to generate, execute:
|
||||||
|
* # openssl req -new -x509 -nodes -sha1 -days 365 -key private.key > certificate.crt
|
||||||
|
* and answer the questions
|
||||||
|
string,certificate_file,"/etc/hermes/hermes.cert"
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
|
* whether to add headers to the email sent or no.
|
||||||
|
* to be rfc compatible this HAS to be true, but if you set to false, no one will know you are using hermes
|
||||||
|
bool,add_headers,true
|
||||||
|
|
||||||
|
* the hostname to use for the headers. useful only in case that gethostname() returns
|
||||||
|
* something that is not correct. For example on windows, it seems to return only the host
|
||||||
|
* part of the name.
|
||||||
|
*
|
||||||
|
* if this is empty, hermes will use the value returned by gethostname()
|
||||||
|
string,hostname,""
|
||||||
|
|
||||||
|
* should a whitelisted hostname or whitelisted ip also disable throttling and banner delaying?
|
||||||
|
* it is useful to make remote hosts deliver mail almost at once
|
||||||
|
bool,whitelisted_disables_everything,true
|
||||||
|
|
||||||
|
* whether to reject connections from hosts that do not provide DNS reverse resolution.
|
||||||
|
* don't enable if you don't know what you are doing or what this switch does
|
||||||
|
bool,reject_no_reverse_resolution,false
|
||||||
|
|
||||||
|
* check whether your ehlo hostname matches your ip reverse resolution.
|
||||||
|
* don't enable unless you understand perfectly what this means
|
||||||
|
bool,check_helo_against_reverse,false
|
||||||
|
|
||||||
|
* whether to query the spf record for the incoming domain.
|
||||||
|
* should help, enable if you have libspf (if you don't, install it and recompile)
|
||||||
|
#ifdef HAVE_SPF
|
||||||
|
bool,query_spf,true
|
||||||
|
#else
|
||||||
|
bool,query_spf,false
|
||||||
|
#endif //HAVE_SPF
|
||||||
|
|
||||||
|
* return temporary error instead of permanent error.
|
||||||
|
* Currently, this only applies to SPF and DNSBL rejected email
|
||||||
|
* You should enable this while debugging your hermes installation,
|
||||||
|
* as configuration errors won't be fatal.
|
||||||
|
bool,return_temp_error_on_reject,false
|
427
src/Database.cpp
Normal file
427
src/Database.cpp
Normal file
|
@ -0,0 +1,427 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include "Database.h"
|
||||||
|
|
||||||
|
void Database::setDatabaseFile(string p_dbfile)
|
||||||
|
{
|
||||||
|
dbfile=p_dbfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* this function executes a query and checks for error
|
||||||
|
* it doesn't return any value, as it is not needed(the queries don't return data)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void Database::doQuery(string p_sql)
|
||||||
|
{
|
||||||
|
int retval;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
retval=sqlite3_exec(dbh,p_sql.c_str(),NULL,NULL,NULL);
|
||||||
|
if(SQLITE_OK!=retval&&SQLITE_BUSY!=retval)
|
||||||
|
throw SQLException("SQL: "+p_sql+" sqlite3_errmsg: "+sqlite3_errmsg(dbh),__FILE__,__LINE__);
|
||||||
|
if(SQLITE_BUSY==retval)
|
||||||
|
{
|
||||||
|
sleep(1+rand()%2);
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << pthread_self() << " doquery() sql failed with busy state, retrying" << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(SQLITE_BUSY==retval);
|
||||||
|
}
|
||||||
|
|
||||||
|
string Database::cleanString(string s)
|
||||||
|
{
|
||||||
|
string result="";
|
||||||
|
|
||||||
|
for(unsigned int i=0;i<s.length();i++)
|
||||||
|
if(s[i]>31&&s[i]<127)
|
||||||
|
switch(s[i])
|
||||||
|
{
|
||||||
|
case ' ':
|
||||||
|
case '<':
|
||||||
|
case '>':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
case '\\':
|
||||||
|
case ',':
|
||||||
|
case ';':
|
||||||
|
case ':':
|
||||||
|
case '"':
|
||||||
|
case '%':
|
||||||
|
case '\'':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result+=s[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::greylisted(string ip,string from,string to,int initial_expiry,int initial_blacklist,int whitelist_expiry)
|
||||||
|
{
|
||||||
|
char **result;
|
||||||
|
int nrow=0;
|
||||||
|
int ncolumn=0;
|
||||||
|
bool retval=true;
|
||||||
|
int sqlite_retval;
|
||||||
|
int now=time(NULL);
|
||||||
|
string strnow=Utils::inttostr(now);
|
||||||
|
string sql="SELECT id,blocked_until FROM greylist WHERE ip=\""+ip+"\" AND emailfrom=\""+from+"\" AND emailto=\""+to+"\" AND "+strnow+" < expires LIMIT 1;";
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
sqlite_retval=sqlite3_get_table(dbh,sql.c_str(),&result,&nrow,&ncolumn,NULL);
|
||||||
|
if(sqlite_retval!=SQLITE_OK&&sqlite_retval!=SQLITE_BUSY)
|
||||||
|
{
|
||||||
|
if(NULL!=result)
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
throw SQLException("SQL: "+sql+" sqlite3_errmsg: "+sqlite3_errmsg(dbh),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
if(SQLITE_BUSY==sqlite_retval)
|
||||||
|
{
|
||||||
|
sleep(1+rand()%2);
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << pthread_self() << " greylisted() sql busy" << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(sqlite_retval==SQLITE_BUSY);
|
||||||
|
|
||||||
|
sql="";
|
||||||
|
|
||||||
|
if(nrow>0)
|
||||||
|
{
|
||||||
|
string id=result[2];
|
||||||
|
//we have seen this triplet before
|
||||||
|
if(now<atol(result[3]))
|
||||||
|
{
|
||||||
|
sql="UPDATE greylist SET blocked=blocked+1 WHERE id="+id+";";
|
||||||
|
doQuery(sql.c_str());
|
||||||
|
retval=true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string expires=Utils::inttostr(now+(60*60*24*whitelist_expiry));
|
||||||
|
|
||||||
|
sql="UPDATE greylist SET expires="+expires+",passed=passed+1 WHERE id="+id+";";
|
||||||
|
doQuery(sql.c_str());
|
||||||
|
retval=false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string blocked_until=Utils::inttostr(now+(60*initial_blacklist));
|
||||||
|
string expires=Utils::inttostr(now+(60*initial_expiry));
|
||||||
|
//new triplet, greylist and add new row
|
||||||
|
retval=true;
|
||||||
|
sql="INSERT INTO greylist(id,ip,emailfrom,emailto,created,blocked_until,expires,passed,blocked)"
|
||||||
|
"VALUES(NULL,\""+ip+"\",\""+from+"\",\""+to+"\","+strnow+","+blocked_until+","+expires+",0,1);";
|
||||||
|
doQuery(sql.c_str());
|
||||||
|
}
|
||||||
|
if(NULL!=result)
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
Database::Database():dbh(NULL)
|
||||||
|
{}
|
||||||
|
|
||||||
|
Database::~Database()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::close()
|
||||||
|
{
|
||||||
|
if(NULL!=dbh)
|
||||||
|
sqlite3_close(dbh);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::_open()
|
||||||
|
{
|
||||||
|
if(sqlite3_open(dbfile.c_str(),&dbh))
|
||||||
|
{
|
||||||
|
dbh=NULL;
|
||||||
|
throw Exception(_("Error creating/opening db ")+dbfile,__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::open()
|
||||||
|
{
|
||||||
|
// if dbfile is new, initialize first
|
||||||
|
if(!Utils::file_exists(dbfile))
|
||||||
|
init();
|
||||||
|
|
||||||
|
_open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::init()
|
||||||
|
{
|
||||||
|
_open();
|
||||||
|
doQuery("CREATE TABLE whitelisted_ips(ip VARCHAR);");
|
||||||
|
doQuery("CREATE TABLE whitelisted_tos(email VARCHAR);");
|
||||||
|
doQuery("CREATE TABLE whitelisted_domains(domain VARCHAR);");
|
||||||
|
doQuery("CREATE TABLE whitelisted_hostnames(hostname VARCHAR);");
|
||||||
|
doQuery("CREATE TABLE blacklisted_tos(email VARCHAR);");
|
||||||
|
doQuery("CREATE TABLE blacklisted_todomains(domain VARCHAR);");
|
||||||
|
doQuery("CREATE TABLE blacklisted_ips(ip VARCHAR);");
|
||||||
|
doQuery("CREATE TABLE blacklisted_froms(email VARCHAR);");
|
||||||
|
doQuery("CREATE TABLE allowed_domains_per_ip(domain VARCHAR,ip VARCHAR);");
|
||||||
|
doQuery("CREATE TABLE greylist(id INTEGER PRIMARY KEY,ip VARCHAR,emailfrom VARCHAR,emailto VARCHAR,created INTEGER,blocked_until INTEGER,expires INTEGER,passed INTEGER,blocked INTEGER);");
|
||||||
|
|
||||||
|
//whitelist localhost
|
||||||
|
doQuery("INSERT INTO whitelisted_ips(ip) VALUES(\"127.0.0.1\");");
|
||||||
|
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
int Database::countRows(string p_sql)
|
||||||
|
{
|
||||||
|
char **result;
|
||||||
|
int nrow=0;
|
||||||
|
int ncolumn=0;
|
||||||
|
int sqlite_retval;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
sqlite_retval=sqlite3_get_table(dbh,p_sql.c_str(),&result,&nrow,&ncolumn,NULL);
|
||||||
|
if(SQLITE_OK!=sqlite_retval&&SQLITE_BUSY!=sqlite_retval)
|
||||||
|
{
|
||||||
|
if(NULL!=result)
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
throw SQLException("SQL: "+p_sql+" sqlite3_errmsg: "+sqlite3_errmsg(dbh),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
if(SQLITE_BUSY==sqlite_retval)
|
||||||
|
{
|
||||||
|
sleep(1+rand()%2);
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << pthread_self() << " countRows() retrying" << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(SQLITE_BUSY==sqlite_retval);
|
||||||
|
|
||||||
|
if(NULL!=result)
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
|
||||||
|
if(ncolumn)
|
||||||
|
return (nrow/ncolumn);
|
||||||
|
else
|
||||||
|
return nrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::whitelistedIP(string p_ip)
|
||||||
|
{
|
||||||
|
string sql="";
|
||||||
|
|
||||||
|
sql="SELECT ip FROM whitelisted_ips WHERE ip=SUBSTR(\""+p_ip+"\",0,LENGTH(ip)) LIMIT 1;";
|
||||||
|
|
||||||
|
if(countRows(sql)>0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::whitelistedTO(string p_email)
|
||||||
|
{
|
||||||
|
string sql="";
|
||||||
|
|
||||||
|
sql="SELECT email FROM whitelisted_tos WHERE email=\""+p_email+"\" LIMIT 1;";
|
||||||
|
|
||||||
|
if(countRows(sql)>0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::whitelistedDomain(string p_domain)
|
||||||
|
{
|
||||||
|
string sql="";
|
||||||
|
|
||||||
|
sql="SELECT domain FROM whitelisted_domains WHERE domain=\""+p_domain+"\" LIMIT 1;";
|
||||||
|
|
||||||
|
if(countRows(sql)>0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::blacklistedTO(string p_email)
|
||||||
|
{
|
||||||
|
string sql="";
|
||||||
|
|
||||||
|
sql="SELECT email FROM blacklisted_tos WHERE email=\""+p_email+"\" LIMIT 1;";
|
||||||
|
|
||||||
|
if(countRows(sql)>0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::blacklistedToDomain(string p_domain)
|
||||||
|
{
|
||||||
|
string sql="";
|
||||||
|
|
||||||
|
sql="SELECT domain FROM blacklisted_todomains WHERE domain=\""+p_domain+"\" LIMIT 1;";
|
||||||
|
|
||||||
|
if(countRows(sql)>0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::blacklistedIP(string p_ip)
|
||||||
|
{
|
||||||
|
string sql="";
|
||||||
|
|
||||||
|
sql="SELECT ip FROM blacklisted_ips WHERE ip=SUBSTR(\""+p_ip+"\",0,LENGTH(ip)) LIMIT 1;";
|
||||||
|
|
||||||
|
if(countRows(sql)>0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::blacklistedFROM(string p_email)
|
||||||
|
{
|
||||||
|
string sql="";
|
||||||
|
|
||||||
|
sql="SELECT email FROM blacklisted_froms WHERE email=\""+p_email+"\" LIMIT 1;";
|
||||||
|
|
||||||
|
if(countRows(sql)>0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::whitelistedHostname(string p_hostname)
|
||||||
|
{
|
||||||
|
string sql="";
|
||||||
|
|
||||||
|
sql="SELECT hostname FROM whitelisted_hostnames WHERE hostname=SUBSTR(\""+p_hostname+"\",-LENGTH(hostname),LENGTH(hostname)) LIMIT 1;";
|
||||||
|
|
||||||
|
if(countRows(sql)>0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Database::allowedDomainPerIP(string p_domain,string p_ip)
|
||||||
|
{
|
||||||
|
string sql="",sql_domain="";
|
||||||
|
|
||||||
|
sql="SELECT ip FROM allowed_domains_per_ip WHERE domain=\""+p_domain+"\" AND ip=\""+p_ip+"\" LIMIT 1;";
|
||||||
|
sql_domain="SELECT ip FROM allowed_domains_per_ip WHERE domain=\""+p_domain+"\" LIMIT 1;";
|
||||||
|
|
||||||
|
if(countRows(sql_domain)>0&&0==countRows(sql))
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this function returns an integer value from a sql
|
||||||
|
* it is useful to calculate things with sql
|
||||||
|
*
|
||||||
|
* i.e.: SELECT SUM(intfield) FROM table
|
||||||
|
*
|
||||||
|
* @param p_sql SQL query to perform
|
||||||
|
*
|
||||||
|
* @return the first value of the first column, rest of data is ignored
|
||||||
|
*/
|
||||||
|
unsigned long Database::getIntValue(string& p_sql)
|
||||||
|
{
|
||||||
|
char **result;
|
||||||
|
int nrow=0;
|
||||||
|
int ncolumn=0;
|
||||||
|
int sqlite_retval;
|
||||||
|
unsigned long value;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
sqlite_retval=sqlite3_get_table(dbh,p_sql.c_str(),&result,&nrow,&ncolumn,NULL);
|
||||||
|
if(SQLITE_OK!=sqlite_retval&&SQLITE_BUSY!=sqlite_retval)
|
||||||
|
{
|
||||||
|
if(NULL!=result)
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
throw SQLException("SQL: "+p_sql+" sqlite3_errmsg: "+sqlite3_errmsg(dbh),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
if(SQLITE_BUSY==sqlite_retval)
|
||||||
|
{
|
||||||
|
sleep(1+rand()%2);
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << pthread_self() << " getIntValue() retrying" << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(SQLITE_BUSY==sqlite_retval);
|
||||||
|
|
||||||
|
if(NULL==result)
|
||||||
|
throw SQLException("SQL: "+p_sql+" didn't return any data, SQL query may be wrong",__FILE__,__LINE__);
|
||||||
|
if('\0'==result[ncolumn])
|
||||||
|
value=0; //why sqlite doesn't return 0 when there are no rows?
|
||||||
|
else
|
||||||
|
value=strtoul(result[ncolumn],NULL,10);
|
||||||
|
sqlite3_free_table(result);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clean the spam database and return the number of spam messages
|
||||||
|
*
|
||||||
|
* @return number of spam messages deleted
|
||||||
|
*/
|
||||||
|
unsigned long Database::cleanDB()
|
||||||
|
{
|
||||||
|
unsigned long spamcount=0; //shut compiler up
|
||||||
|
string sql;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//block database until we have finished cleaning it
|
||||||
|
doQuery("BEGIN EXCLUSIVE TRANSACTION");
|
||||||
|
|
||||||
|
//now count how many blocked emails we have to submit to stats
|
||||||
|
//we do it always because if we don't submit stats it stills appears on the logs
|
||||||
|
sql="SELECT SUM(blocked) FROM greylist WHERE expires<strftime('%s','now') AND passed=0;";
|
||||||
|
spamcount=getIntValue(sql);
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "We have processed " << spamcount << " spam emails in the last 4 hours" << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
|
||||||
|
//at last, delete them from the database
|
||||||
|
doQuery("DELETE FROM greylist WHERE expires<strftime('%s','now');");
|
||||||
|
|
||||||
|
//and close the transaction
|
||||||
|
doQuery("COMMIT TRANSACTION");
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
doQuery("ROLLBACK TRANSACTION");
|
||||||
|
}
|
||||||
|
|
||||||
|
return spamcount;
|
||||||
|
}
|
67
src/Database.h
Normal file
67
src/Database.h
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef DATABASE_H
|
||||||
|
#define DATABASE_H
|
||||||
|
|
||||||
|
#include "hermes.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this class implements an interface to the sqlite3 database
|
||||||
|
*/
|
||||||
|
class Database
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
string dbfile;
|
||||||
|
sqlite3 *dbh;
|
||||||
|
void _open();
|
||||||
|
int countRows(string);
|
||||||
|
void doQuery(string);
|
||||||
|
unsigned long getIntValue(string&);
|
||||||
|
public:
|
||||||
|
Database();
|
||||||
|
~Database();
|
||||||
|
void setDatabaseFile(string);
|
||||||
|
static string cleanString(string);
|
||||||
|
void init();
|
||||||
|
void open();
|
||||||
|
void close();
|
||||||
|
bool greylisted(string,string,string,int,int,int);
|
||||||
|
bool whitelistedIP(string);
|
||||||
|
bool whitelistedHostname(string);
|
||||||
|
bool whitelistedTO(string);
|
||||||
|
bool whitelistedDomain(string);
|
||||||
|
bool blacklistedTO(string);
|
||||||
|
bool blacklistedToDomain(string);
|
||||||
|
bool blacklistedIP(string);
|
||||||
|
bool blacklistedFROM(string);
|
||||||
|
bool allowedDomainPerIP(string,string);
|
||||||
|
unsigned long cleanDB();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //DATABASE_H
|
146
src/Exception.cpp
Normal file
146
src/Exception.cpp
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include "Exception.h"
|
||||||
|
|
||||||
|
extern Configfile cfg;
|
||||||
|
extern LOGGER_CLASS hermes_log;
|
||||||
|
|
||||||
|
Exception::Exception(string p_error,string p_file,unsigned p_line)
|
||||||
|
{
|
||||||
|
error=p_error;
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
hermes_log.addMessage(LOG_ERR,p_error);
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
#ifdef NOTIFY_EXCEPTIONS
|
||||||
|
if(cfg.getNotifyTo()!="")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
notifyByEmail("Exception: "+p_error);
|
||||||
|
}
|
||||||
|
catch(NotifyException &e)
|
||||||
|
{
|
||||||
|
cout << string(e) << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif //NOTIFY_EXCEPTIONS
|
||||||
|
file=p_file;
|
||||||
|
line=p_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
Exception::operator string()
|
||||||
|
{
|
||||||
|
return file+":"+Utils::inttostr(line)+": "+error;
|
||||||
|
}
|
||||||
|
|
||||||
|
ostream& operator<<(ostream &os,Exception &e)
|
||||||
|
{
|
||||||
|
os << string(e);
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef NOTIFY_EXCEPTIONS
|
||||||
|
#ifdef USE_SMTP_FOR_NOTIFICATIONS
|
||||||
|
/**
|
||||||
|
* notify problem by email to address, using an smtp server
|
||||||
|
*
|
||||||
|
* it will add some text before the message to be identificable
|
||||||
|
* this function only makes mild error-checks on codes returned
|
||||||
|
* by server, so some email might not reach destination
|
||||||
|
* this functions' mission in life is help those poor souls still
|
||||||
|
* using windows(and others), as they just don't have sendmail or popen,
|
||||||
|
* wich is way faster, way less complicated and much less error-prone
|
||||||
|
* also if you're using chroot, you might want to use smtp instead of sendmail
|
||||||
|
* as it is a much simpler setup
|
||||||
|
*
|
||||||
|
* @param string message message of the problem
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
void Exception::notifyByEmail(string message)
|
||||||
|
{
|
||||||
|
Socket smtpServer;
|
||||||
|
string address=cfg.getNotifyTo();
|
||||||
|
string response;
|
||||||
|
|
||||||
|
if(""==address)
|
||||||
|
throw Exception(_("Notification address is incorrect or empty, please check"),__FILE__,__LINE__);
|
||||||
|
smtpServer.init();
|
||||||
|
smtpServer.connect(cfg.getServerHost(),cfg.getServerPort());
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
if(cfg.getOutgoingSSL())
|
||||||
|
smtpServer.enableSSL(false);
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
|
#define ERROR_SENDING(x,y,z) if(x.substr(0,3)!=y) throw NotifyException(_("Error "+x.substr(0,3)+" sending email after ")+z);
|
||||||
|
#define GET_RESPONSE_AND_CHECK(x,y) response=smtpServer.readLine(); ERROR_SENDING(response,x,y); cout << "s:" << response << endl;
|
||||||
|
#define SEND_COMMAND_AND_CHECK(x,y,z) smtpServer.writeLine(x); cout << "c:" << x << endl; GET_RESPONSE_AND_CHECK(y,z);
|
||||||
|
#define DEBUGSRV() //if(smtpServer.canRead(1)) cout << "s:" << smtpServer.readLine() << endl;
|
||||||
|
|
||||||
|
GET_RESPONSE_AND_CHECK("220","CONNECT");
|
||||||
|
|
||||||
|
SEND_COMMAND_AND_CHECK("HELO localhost","250","EHLO");
|
||||||
|
SEND_COMMAND_AND_CHECK("MAIL FROM: \"hermes daemon\"<hermes@localhost>","250","MAIL FROM");
|
||||||
|
SEND_COMMAND_AND_CHECK("RCPT TO: "+address,"250","RCPT TO");
|
||||||
|
SEND_COMMAND_AND_CHECK("DATA","354","DATA");
|
||||||
|
|
||||||
|
smtpServer.writeLine("From: \"hermes daemon\"<hermes@localhost>");
|
||||||
|
DEBUGSRV();
|
||||||
|
smtpServer.writeLine("To: "+address);
|
||||||
|
DEBUGSRV();
|
||||||
|
smtpServer.writeLine("Subject: Exception happened on hermes");
|
||||||
|
DEBUGSRV();
|
||||||
|
smtpServer.writeLine("");
|
||||||
|
DEBUGSRV();
|
||||||
|
smtpServer.writeLine("Hello "+address+",");
|
||||||
|
DEBUGSRV();
|
||||||
|
smtpServer.writeLine("Hermes gave an error, with the following error message:");
|
||||||
|
DEBUGSRV();
|
||||||
|
smtpServer.writeLine(message);
|
||||||
|
DEBUGSRV();
|
||||||
|
smtpServer.writeLine(".");
|
||||||
|
GET_RESPONSE_AND_CHECK("250","sending data contents");
|
||||||
|
SEND_COMMAND_AND_CHECK("QUIT","221","QUIT");
|
||||||
|
|
||||||
|
#undef ERROR_SENDING
|
||||||
|
#undef GET_RESPONSE_AND_CHECK
|
||||||
|
#undef SEND_COMMAND_AND_CHECK
|
||||||
|
}
|
||||||
|
#else //THEN USE sendmail to send the email
|
||||||
|
void Exception::notifyByEmail(string message)
|
||||||
|
{
|
||||||
|
FILE *sendmail;
|
||||||
|
string address=cfg.getNotifyTo();
|
||||||
|
|
||||||
|
sendmail=popen("/var/qmail/bin/sendmail -t","w");
|
||||||
|
if(NULL==sendmail)
|
||||||
|
throw NotifyException(_("Couldn't initialize sendmail command"));
|
||||||
|
fprintf(sendmail,"From: \"hermes daemon\"<hermes@localhost>\n");
|
||||||
|
fprintf(sendmail,"To: %s\n",address.c_str());
|
||||||
|
fprintf(sendmail,"Subject: Exception happened on hermes\n");
|
||||||
|
fprintf(sendmail,"\n");
|
||||||
|
fprintf(sendmail,"Hello %s,\n",address.c_str());
|
||||||
|
fprintf(sendmail,"Hermes gave an error, with the following error message:\n");
|
||||||
|
fprintf(sendmail,"%s\n",message.c_str());
|
||||||
|
fflush(sendmail);
|
||||||
|
pclose(sendmail);
|
||||||
|
}
|
||||||
|
#endif //USE_SMTP_FOR_NOTIFICATIONS
|
||||||
|
#endif //NOTIFY_EXCEPTIONS
|
64
src/Exception.h
Normal file
64
src/Exception.h
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef EXCEPTION_H
|
||||||
|
#define EXCEPTION_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "Configfile.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
class Exception
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
string error;
|
||||||
|
string file;
|
||||||
|
unsigned line;
|
||||||
|
void notifyByEmail(string);
|
||||||
|
public:
|
||||||
|
Exception(string,string,unsigned);
|
||||||
|
operator string();
|
||||||
|
friend ostream& operator<<(ostream&,Exception&);
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkException:public Exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NetworkException(string p_error,string p_file,int p_line):Exception(p_error,p_file,p_line){}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SQLException:public Exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SQLException(string p_error,string p_file,int p_line):Exception(p_error,p_file,p_line){}
|
||||||
|
};
|
||||||
|
|
||||||
|
class NotifyException
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
string error;
|
||||||
|
public:
|
||||||
|
NotifyException(string p_error){ error=p_error; }
|
||||||
|
operator string(){ return "NotifyException: "+error;};
|
||||||
|
};
|
||||||
|
#endif //EXCEPTION_H
|
156
src/FileLogger.cpp
Normal file
156
src/FileLogger.cpp
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include "FileLogger.h"
|
||||||
|
|
||||||
|
extern Configfile cfg;
|
||||||
|
|
||||||
|
FileLogger::FileLogger():linecount(0)
|
||||||
|
{
|
||||||
|
pthread_mutex_init(&mutex,NULL);
|
||||||
|
tmpstrings.clear();
|
||||||
|
last_rotation=time(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileLogger::~FileLogger()
|
||||||
|
{
|
||||||
|
if(!cfg.getKeepFileLocked())
|
||||||
|
syncBufferToDisk();
|
||||||
|
closeFile();
|
||||||
|
pthread_mutex_destroy(&mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLogger::openFile(string file)
|
||||||
|
{
|
||||||
|
if(NULL==f&&""!=file)
|
||||||
|
{
|
||||||
|
f=fopen(file.c_str(),"a");
|
||||||
|
if(NULL==f)
|
||||||
|
throw Exception(_("Couldn't start file logger, couldn't open ")+cfg.getFileLoggerFilename(),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLogger::closeFile()
|
||||||
|
{
|
||||||
|
if(NULL!=f)
|
||||||
|
{
|
||||||
|
fclose(f);
|
||||||
|
f=NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLogger::syncBufferToDisk()
|
||||||
|
{
|
||||||
|
openFile(cfg.getFileLoggerFilename());
|
||||||
|
if(NULL!=f)
|
||||||
|
{
|
||||||
|
for(list<string>::iterator i=tmpstrings.begin();i!=tmpstrings.end();i++)
|
||||||
|
fprintf(f,"%s\n",i->c_str());
|
||||||
|
closeFile();
|
||||||
|
tmpstrings.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLogger::addMessage(int loglevel,string logmessage)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
if(cfg.getLogRotationFrequency()>0&&last_rotation+(cfg.getLogRotationFrequency()*60)<time(NULL))
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "Rotating log to file " << getProcessedRotateFilename() << " at " << time(NULL) << " with a last rotation of " << last_rotation << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
rotateLog();
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(!cfg.getKeepFileLocked())
|
||||||
|
tmpstrings.push_back(Utils::rfc2821_date()+": "+logmessage);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
openFile(cfg.getFileLoggerFilename());
|
||||||
|
if(NULL!=f)
|
||||||
|
fprintf(f,"%s: %s\n",Utils::rfc2821_date().c_str(),logmessage.c_str());
|
||||||
|
}
|
||||||
|
if(++linecount>30)
|
||||||
|
{
|
||||||
|
linecount=0;
|
||||||
|
if(!cfg.getKeepFileLocked())
|
||||||
|
syncBufferToDisk();
|
||||||
|
else if(NULL!=f)
|
||||||
|
fflush(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLogger::rotateLog()
|
||||||
|
{
|
||||||
|
string filename="";
|
||||||
|
|
||||||
|
if(!cfg.getKeepFileLocked())
|
||||||
|
syncBufferToDisk();
|
||||||
|
|
||||||
|
closeFile();
|
||||||
|
|
||||||
|
filename=getProcessedRotateFilename();
|
||||||
|
if(-1==rename(cfg.getFileLoggerFilename().c_str(),filename.c_str()))
|
||||||
|
throw Exception("Error renaming logfile to "+filename+": "+Utils::errnotostrerror(errno),__FILE__,__LINE__);
|
||||||
|
last_rotation=time(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SUBSTITUTE(x) \
|
||||||
|
sizetemp=tmpstr.find("%%" #x "%%");\
|
||||||
|
if(sizetemp!=string::npos)\
|
||||||
|
tmpstr.replace(sizetemp,strlen("%%" #x "%%"),x);
|
||||||
|
|
||||||
|
#define GETDATECHR(x,y) \
|
||||||
|
strftime(tmpchar,sizeof(tmpchar),x,local_time);\
|
||||||
|
y=tmpchar;
|
||||||
|
|
||||||
|
string FileLogger::getProcessedRotateFilename()
|
||||||
|
{
|
||||||
|
string tmpstr=cfg.getRotateFilename();
|
||||||
|
string::size_type sizetemp;
|
||||||
|
string year,month,day,hour,minute;
|
||||||
|
char tmpchar[5];
|
||||||
|
struct tm *local_time;
|
||||||
|
time_t t;
|
||||||
|
|
||||||
|
t=time(NULL);
|
||||||
|
local_time=localtime(&t);
|
||||||
|
|
||||||
|
GETDATECHR("%Y",year);
|
||||||
|
GETDATECHR("%m",month);
|
||||||
|
GETDATECHR("%d",day);
|
||||||
|
GETDATECHR("%H",hour);
|
||||||
|
GETDATECHR("%M",minute);
|
||||||
|
|
||||||
|
SUBSTITUTE(day);
|
||||||
|
SUBSTITUTE(month);
|
||||||
|
SUBSTITUTE(year);
|
||||||
|
SUBSTITUTE(hour);
|
||||||
|
SUBSTITUTE(minute);
|
||||||
|
|
||||||
|
return tmpstr;
|
||||||
|
}
|
58
src/FileLogger.h
Normal file
58
src/FileLogger.h
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef FILELOGGER_H
|
||||||
|
#define FILELOGGER_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <string>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "Configfile.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this class implements a logger that writes to a file
|
||||||
|
*
|
||||||
|
* @see Logger
|
||||||
|
*/
|
||||||
|
class FileLogger: public Logger
|
||||||
|
{
|
||||||
|
unsigned char linecount;
|
||||||
|
time_t last_rotation;
|
||||||
|
private:
|
||||||
|
FILE *f;
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
list<string> tmpstrings;
|
||||||
|
void openFile(string);
|
||||||
|
void closeFile();
|
||||||
|
void syncBufferToDisk();
|
||||||
|
void rotateLog();
|
||||||
|
string getProcessedRotateFilename();
|
||||||
|
public:
|
||||||
|
FileLogger();
|
||||||
|
~FileLogger();
|
||||||
|
void addMessage(int,string);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FILELOGGER_H
|
49
src/Logger.h
Normal file
49
src/Logger.h
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef LOGGER_H
|
||||||
|
#define LOGGER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* logger base class
|
||||||
|
*/
|
||||||
|
class Logger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Logger(){}; //empty destructor, not creating anything
|
||||||
|
virtual void addMessage(int,string)=0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
#include "UnixLogger.h"
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
#ifndef LOG_INFO
|
||||||
|
#define LOG_INFO 0
|
||||||
|
#define LOG_DEBUG 1
|
||||||
|
#define LOG_ERR 2
|
||||||
|
#endif //LOG_INFO
|
||||||
|
#include "FileLogger.h"
|
||||||
|
#include "NullLogger.h"
|
||||||
|
|
||||||
|
#endif //LOGGER_H
|
33
src/Makefile.am
Normal file
33
src/Makefile.am
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
INCLUDES = $(OpenSSL_CFLAGS) $(SQLite3_CFLAGS)
|
||||||
|
LIBS = $(OpenSSL_LIBS) $(SQLite3_LIBS)
|
||||||
|
CXXFLAGS += -Wall -ansi -pedantic -Wshadow -pthread
|
||||||
|
|
||||||
|
bin_PROGRAMS = hermes
|
||||||
|
nodist_hermes_SOURCES = Configfile.cpp
|
||||||
|
hermes_SOURCES = Proxy.cpp ServerSocket.cpp Socket.cpp Database.cpp Utils.cpp Exception.cpp hermes.cpp
|
||||||
|
noinst_HEADERS = Proxy.h ServerSocket.h Socket.h Database.h UnixLogger.cpp FileLogger.h NullLogger.h Logger.h Utils.h Exception.h hermes.h Spf.h
|
||||||
|
EXTRA_DIST = Configfile.cpp.in Configfile.h.in Configfile.tmpl UnixLogger.cpp UnixLogger.h FileLogger.cpp FileLogger.h
|
||||||
|
|
||||||
|
if LOGGER_UNIX
|
||||||
|
hermes_SOURCES += UnixLogger.cpp
|
||||||
|
endif
|
||||||
|
|
||||||
|
if LOGGER_FILE
|
||||||
|
hermes_SOURCES += FileLogger.cpp
|
||||||
|
endif
|
||||||
|
|
||||||
|
if HAVE_SPF
|
||||||
|
hermes_SOURCES += Spf.cpp
|
||||||
|
LIBS += -lspf2
|
||||||
|
endif
|
||||||
|
|
||||||
|
if WIN32_SERVICE
|
||||||
|
hermes_SOURCES += win32-service.cpp
|
||||||
|
endif
|
||||||
|
|
||||||
|
Configfile.h: Configfile.cpp.in Configfile.h.in Configfile.tmpl ../docs/hermes-options.html.in ../scripts/generate_config.pl ../config.h
|
||||||
|
cpp Configfile.tmpl|../scripts/generate_config.pl
|
||||||
|
|
||||||
|
Configfile.cpp: Configfile.h Configfile.cpp.in Configfile.h.in Configfile.tmpl ../docs/hermes-options.html.in ../scripts/generate_config.pl ../config.h
|
||||||
|
|
||||||
|
*.cpp: Configfile.h
|
41
src/NullLogger.h
Normal file
41
src/NullLogger.h
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef NULLLOGGER_H
|
||||||
|
#define NULLLOGGER_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this logger implements a null logger
|
||||||
|
* messages added to this logger go nowhere
|
||||||
|
*
|
||||||
|
* @see Logger
|
||||||
|
*/
|
||||||
|
class NullLogger: public Logger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void addMessage(int,string){}; //ignore messages
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //NULLLOGGER_H
|
334
src/Proxy.cpp
Normal file
334
src/Proxy.cpp
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include "Proxy.h"
|
||||||
|
|
||||||
|
extern LOGGER_CLASS hermes_log;
|
||||||
|
extern Configfile cfg;
|
||||||
|
|
||||||
|
void Proxy::setOutside(Socket& p_outside)
|
||||||
|
{
|
||||||
|
outside=p_outside;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this function is the main part of the program, it just sniffs traffic
|
||||||
|
* between server and client and acts acording to the following diagram:
|
||||||
|
*
|
||||||
|
* TODO: fill diagram and point to website with graphical version
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void Proxy::run(string &peer_address)
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
string debugLog="\r\n";
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
|
||||||
|
#ifdef HAVE_SPF
|
||||||
|
Spf spf_checker;
|
||||||
|
#endif //HAVE_SPF
|
||||||
|
|
||||||
|
string from="";
|
||||||
|
string to="";
|
||||||
|
string ehlostr="";
|
||||||
|
string resolvedname="";
|
||||||
|
unsigned char last_state=SMTP_STATE_WAIT_FOR_HELO;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool throttled=cfg.getThrottle(); //we start with a throttled connection
|
||||||
|
bool authenticated=false; //we start with a non-authenticated connection
|
||||||
|
bool esmtp=false;
|
||||||
|
string strtemp;
|
||||||
|
|
||||||
|
//check whitelist
|
||||||
|
if(!cfg.getDnsWhitelistDomains().empty()&&Utils::listed_on_dns_lists(cfg.getDnsWhitelistDomains(),cfg.getDnsWhitelistPercentage(),peer_address))
|
||||||
|
{
|
||||||
|
authenticated=true;
|
||||||
|
if(true==cfg.getWhitelistedDisablesEverything())
|
||||||
|
throttled=false;
|
||||||
|
}
|
||||||
|
if(true==cfg.getWhitelistedDisablesEverything()&&Utils::whitelisted(cfg.getDatabaseFile(),peer_address))
|
||||||
|
{
|
||||||
|
throttled=false;
|
||||||
|
authenticated=true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(false==cfg.getAllowDataBeforeBanner())
|
||||||
|
{
|
||||||
|
sleep(cfg.getBannerDelayTime());
|
||||||
|
if(outside.canRead(0)) //if we have data waiting before the server gives us a 220 then quit, it's spam
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << peer_address << " sent data before 220, exiting..." << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
hermes_log.addMessage(LOG_INFO,"421 (data_before_banner) (ip:"+peer_address+")");
|
||||||
|
sleep(20); // but first let's annoy spammers once more
|
||||||
|
outside.writeLine("421 Stop sending data before we show you the banner");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inside.init();
|
||||||
|
inside.connect(cfg.getServerHost(),cfg.getServerPort());
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
if(cfg.getOutgoingSsl())
|
||||||
|
inside.enableSSL(false);
|
||||||
|
if(cfg.getIncomingSsl())
|
||||||
|
outside.enableSSL(true);
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
|
while(!outside.isClosed()&&!inside.isClosed())
|
||||||
|
{
|
||||||
|
if(outside.canRead(0.2)) //client wants to send something to server
|
||||||
|
{
|
||||||
|
strtemp=outside.readLine();
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
debugLog+="c> "+strtemp+"\r\n";
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
if(outside.isClosed())
|
||||||
|
return;
|
||||||
|
if(strtemp.length()>10&&"mail from:"==Utils::strtolower(strtemp.substr(0,10)))
|
||||||
|
{
|
||||||
|
from=Utils::getmail(strtemp);
|
||||||
|
last_state=SMTP_STATE_WAIT_FOR_RCPTTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if("ehlo"==Utils::strtolower(strtemp.substr(0,4)))
|
||||||
|
esmtp=true;
|
||||||
|
|
||||||
|
if(strtemp.length()>4&&("ehlo"==Utils::strtolower(strtemp.substr(0,4))||"helo"==Utils::strtolower(strtemp.substr(0,4))))
|
||||||
|
{
|
||||||
|
ehlostr=Utils::trim(strtemp.substr(5));
|
||||||
|
last_state=SMTP_STATE_WAIT_FOR_MAILFROM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strtemp.length()>8&&"rcpt to:"==Utils::strtolower(strtemp.substr(0,8)))
|
||||||
|
{
|
||||||
|
string strlog="";
|
||||||
|
string code="";
|
||||||
|
string mechanism="";
|
||||||
|
string message="";
|
||||||
|
|
||||||
|
to=Utils::getmail(strtemp);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
resolvedname=Socket::resolveInverselyToString(peer_address);
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
resolvedname="";
|
||||||
|
}
|
||||||
|
|
||||||
|
strlog="from "+from+" (ip:"+peer_address+", hostname:"+resolvedname+", "+(esmtp?"ehlo":"helo")+":"+ehlostr+") -> to "+to;
|
||||||
|
|
||||||
|
//check greylisting
|
||||||
|
if(cfg.getGreylist()&&!authenticated&&Utils::greylist(cfg.getDatabaseFile(),peer_address,from,to))
|
||||||
|
{
|
||||||
|
//should we greylist¿? if we have to, quit and then sleep 20 seconds before closing the connection
|
||||||
|
code="421";
|
||||||
|
mechanism="greylist";
|
||||||
|
message=code+" Greylisted!! Please try again in a few minutes.";
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "Checking " << mechanism << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
#ifdef HAVE_SPF
|
||||||
|
else if(cfg.getQuerySpf()&&!authenticated&&!spf_checker.query(peer_address,ehlostr,from))
|
||||||
|
{
|
||||||
|
code=cfg.getReturnTempErrorOnReject()?"421":"550";
|
||||||
|
mechanism="spf";
|
||||||
|
message=code+" You do not seem to be allowed to send email for that particular domain.";
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "Checking " << mechanism << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
#endif //HAVE_SPF
|
||||||
|
//check blacklist
|
||||||
|
else if(!authenticated&&Utils::blacklisted(cfg.getDatabaseFile(),peer_address,to))
|
||||||
|
{
|
||||||
|
code=cfg.getReturnTempErrorOnReject()?"421":"550";
|
||||||
|
mechanism="allowed-domain-per-ip";
|
||||||
|
message=code+" You do not seem to be allowed to send email to that particular domain from that address.";
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "Checking " << mechanism << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
//check rbl
|
||||||
|
else if(!cfg.getDnsBlacklistDomains().empty()&&!authenticated&&Utils::listed_on_dns_lists(cfg.getDnsBlacklistDomains(),cfg.getDnsBlacklistPercentage(),peer_address))
|
||||||
|
{
|
||||||
|
code=cfg.getReturnTempErrorOnReject()?"421":"550";
|
||||||
|
mechanism="dnsbl";
|
||||||
|
message=code+" You are listed on some DNS blacklists. Get delisted before trying to send us email.";
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "Checking " << mechanism << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
else if(cfg.getRejectNoReverseResolution()&&!authenticated&&""==resolvedname)
|
||||||
|
{
|
||||||
|
code=cfg.getReturnTempErrorOnReject()?"421":"550";
|
||||||
|
mechanism="no reverse resolution";
|
||||||
|
message=code+" Your IP address does not resolve to a hostname.";
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "Checking " << mechanism << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
else if(cfg.getCheckHeloAgainstReverse()&&!authenticated&&ehlostr!=resolvedname)
|
||||||
|
{
|
||||||
|
code=cfg.getReturnTempErrorOnReject()?"421":"550";
|
||||||
|
mechanism="helo differs from resolved name";
|
||||||
|
message=code+" Your IP hostname doesn't match your envelope hostname.";
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "Checking " << mechanism << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
else
|
||||||
|
code="250";
|
||||||
|
|
||||||
|
if(""!=mechanism)
|
||||||
|
strlog.insert(0,"("+mechanism+") ");
|
||||||
|
strlog.insert(0,code+" ");
|
||||||
|
|
||||||
|
//log the connection
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << strlog << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
hermes_log.addMessage(LOG_INFO,strlog);
|
||||||
|
|
||||||
|
//if we didn't accept the email, punish spammers
|
||||||
|
if("250"!=code)
|
||||||
|
{
|
||||||
|
inside.writeLine("QUIT");
|
||||||
|
inside.close(); //close the socket now and leave server alone
|
||||||
|
sleep(20);
|
||||||
|
outside.writeLine(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
last_state=SMTP_STATE_WAIT_FOR_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
if("starttls"==Utils::strtolower(strtemp.substr(0,8)))
|
||||||
|
{
|
||||||
|
//if we have ssl then accept starttls, if not politely say fuck you
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << peer_address << " enabled starttls, using secure comunications!!!" << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
outside.writeLine("220 You can speak now, line is secure!!");
|
||||||
|
outside.enableSSL(true);
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << string(e) << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
hermes_log.addMessage(LOG_DEBUG,string(e));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
outside.writeLine("454 TLS temporarily not available");
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
strtemp="";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(strtemp.length())
|
||||||
|
inside.writeLine(strtemp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(inside.canRead(0.2)) //server wants to send something to client
|
||||||
|
{
|
||||||
|
strtemp=inside.readLine();
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
debugLog+="s> "+strtemp+"\r\n";
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
if(inside.isClosed())
|
||||||
|
return;
|
||||||
|
string code=strtemp.substr(0,3); //all responses by the server start with a code
|
||||||
|
|
||||||
|
if("354"==code) //354 -> you can start sending data, unthrottle now and read binary-safe
|
||||||
|
{
|
||||||
|
string endofdata="";
|
||||||
|
ssize_t bytes_read=0;
|
||||||
|
char buffer[4097];
|
||||||
|
|
||||||
|
outside.writeLine(strtemp);
|
||||||
|
strtemp="";
|
||||||
|
if(cfg.getAddHeaders())
|
||||||
|
{
|
||||||
|
inside.writeLine("Received: from "+ehlostr+" ("+peer_address+")");
|
||||||
|
inside.writeLine(" by "+Utils::gethostname()+" with "+(esmtp?"ESTMP":"SMTP")+" via TCP; "+Utils::rfc2821_date());
|
||||||
|
inside.writeLine("X-Anti-Spam-Proxy: Proxied by Hermes [www.hermes-project.com]");
|
||||||
|
}
|
||||||
|
do
|
||||||
|
{
|
||||||
|
bytes_read=outside.readBytes(buffer,sizeof(buffer)-1);
|
||||||
|
if(bytes_read<1)
|
||||||
|
throw NetworkException("Problem reading DATA contents, recv returned "+Utils::inttostr(bytes_read),__FILE__,__LINE__);
|
||||||
|
buffer[bytes_read]='\0';
|
||||||
|
inside.writeBytes(buffer,bytes_read);
|
||||||
|
if(bytes_read<5)
|
||||||
|
endofdata+=string(buffer);
|
||||||
|
else
|
||||||
|
endofdata=string(buffer+bytes_read-5);
|
||||||
|
if(endofdata.length()>5)
|
||||||
|
endofdata=endofdata.substr(endofdata.length()-5);
|
||||||
|
}
|
||||||
|
while(endofdata!="\r\n.\r\n"/*&&endofdata.length()>3&&endofdata.substr(2)!="\n.\n"&&endofdata.substr(2)!="\r.\r"*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
if("235"==code) //235 -> you are correctly authenticated, unthrottle & authenticate
|
||||||
|
{
|
||||||
|
throttled=false;
|
||||||
|
authenticated=true;
|
||||||
|
}
|
||||||
|
if("250-pipelining"==Utils::strtolower(strtemp)) //this solves our problems with pipelining-enabled servers
|
||||||
|
strtemp="";
|
||||||
|
|
||||||
|
//this is a special case, we can't just ignore the line if it's the last line (doesn't have the dash after the code)
|
||||||
|
//so we just say we support an imaginary extension (noextension).
|
||||||
|
//caveat: this makes us identificable, so, if you can, configure your smtp server to either don't support pipelining
|
||||||
|
//or to not advertise it as the last capability.
|
||||||
|
if("250 pipelining"==Utils::strtolower(strtemp))
|
||||||
|
strtemp="250 x-noextension";
|
||||||
|
if(strtemp.length())
|
||||||
|
outside.writeLine(strtemp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(throttled)
|
||||||
|
sleep(cfg.getThrottlingTime()); //we take 1 second between each command to make spammers angry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception &e) //any exception will close both connections
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << e << endl;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
throw Exception(string(e)+debugLog+"\r\n",__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
catch(Exception &ex){}
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
if(last_state<SMTP_STATE_WAIT_FOR_DATA)
|
||||||
|
hermes_log.addMessage(LOG_INFO,"421 (throttling) from "+(""==from?"no-from":from)+" (ip:"+peer_address+", hostname:"+(""==resolvedname?"not-resolved":resolvedname)+", ehlo:"+(""==ehlostr?"no-ehlo":ehlostr)+") -> to "+(""==to?"no-to":to));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
61
src/Proxy.h
Normal file
61
src/Proxy.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef PROXY_H
|
||||||
|
#define PROXY_H
|
||||||
|
|
||||||
|
#include "hermes.h"
|
||||||
|
#include <sys/param.h>
|
||||||
|
#ifdef WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#else
|
||||||
|
#include <sys/select.h>
|
||||||
|
#endif
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "Socket.h"
|
||||||
|
#include "Configfile.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#ifdef HAVE_SPF
|
||||||
|
#include "Spf.h"
|
||||||
|
#endif //HAVE_SPF
|
||||||
|
|
||||||
|
#define SMTP_STATE_WAIT_FOR_HELO 0
|
||||||
|
#define SMTP_STATE_WAIT_FOR_MAILFROM 1
|
||||||
|
#define SMTP_STATE_WAIT_FOR_RCPTTO 2
|
||||||
|
#define SMTP_STATE_WAIT_FOR_DATA 3
|
||||||
|
|
||||||
|
class Proxy
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Socket outside; //connection from someone sending mail
|
||||||
|
Socket inside; //connection to our inside smtp
|
||||||
|
public:
|
||||||
|
//Proxy():outside(NULL),inside(NULL){};
|
||||||
|
void setOutside(Socket&);
|
||||||
|
void run(string&);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //PROXY_H
|
88
src/ServerSocket.cpp
Normal file
88
src/ServerSocket.cpp
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include "ServerSocket.h"
|
||||||
|
|
||||||
|
void ServerSocket::setPort(unsigned int p_port)
|
||||||
|
{
|
||||||
|
port=p_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerSocket::listen()
|
||||||
|
{
|
||||||
|
struct sockaddr_in address;
|
||||||
|
|
||||||
|
address.sin_family=AF_INET;
|
||||||
|
#ifndef WIN32
|
||||||
|
if("any"==listen_ip||""==listen_ip)
|
||||||
|
address.sin_addr.s_addr=INADDR_ANY;
|
||||||
|
else
|
||||||
|
if(!inet_aton(listen_ip.c_str(),&address.sin_addr))
|
||||||
|
throw Exception(_("IP address ")+listen_ip+_(" is not valid"),__FILE__,__LINE__);
|
||||||
|
#else
|
||||||
|
address.sin_addr.s_addr=INADDR_ANY;
|
||||||
|
#endif //WIN32
|
||||||
|
address.sin_port=htons(port);
|
||||||
|
|
||||||
|
// ...and allow reuse of the socket
|
||||||
|
int i=1;
|
||||||
|
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&i,sizeof(i));
|
||||||
|
|
||||||
|
if(bind(fd,(sockaddr*)&address,sizeof(address))==-1)
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
throw Exception(_("Error: binding to address ")+(""==listen_ip?"any":listen_ip)+":"+Utils::inttostr(port),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(::listen(fd,10)==-1)
|
||||||
|
throw Exception(_("Error: listening"),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convenience wrapper for listen
|
||||||
|
*
|
||||||
|
* @param port port to listen at
|
||||||
|
* @param ip ip to bind to
|
||||||
|
*/
|
||||||
|
void ServerSocket::listen(unsigned int p_port,string ip)
|
||||||
|
{
|
||||||
|
setPort(p_port);
|
||||||
|
setListenIP(ip);
|
||||||
|
listen();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerSocket::setListenIP(string& ip)
|
||||||
|
{
|
||||||
|
listen_ip=ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ServerSocket::accept(string *straddr)
|
||||||
|
{
|
||||||
|
struct sockaddr_in address;
|
||||||
|
socklen_t addresslength=sizeof(address);
|
||||||
|
int retval;
|
||||||
|
retval=::accept(fd,(sockaddr *)&address,&addresslength);
|
||||||
|
|
||||||
|
if(-1==retval)
|
||||||
|
throw Exception(_(Utils::inttostr(retval)),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
if(straddr!=NULL)
|
||||||
|
(*straddr)=inet_ntoa(address.sin_addr);
|
||||||
|
return retval;
|
||||||
|
}
|
57
src/ServerSocket.h
Normal file
57
src/ServerSocket.h
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef SERVERSOCKET_H
|
||||||
|
#define SERVERSOCKET_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#ifdef WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#else
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <libintl.h>
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "hermes.h"
|
||||||
|
#include "Socket.h"
|
||||||
|
/**
|
||||||
|
* implement the server specific methods for socket, mainly listen() and accept()
|
||||||
|
* @see Socket
|
||||||
|
*/
|
||||||
|
class ServerSocket: public Socket
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
unsigned int port;
|
||||||
|
string listen_ip;
|
||||||
|
public:
|
||||||
|
ServerSocket(){};
|
||||||
|
~ServerSocket(){};
|
||||||
|
void setPort(unsigned int);
|
||||||
|
void setListenIP(string&);
|
||||||
|
void listen();
|
||||||
|
void listen(unsigned int,string);
|
||||||
|
int accept(string *);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //SERVERSOCKET_H
|
577
src/Socket.cpp
Normal file
577
src/Socket.cpp
Normal file
|
@ -0,0 +1,577 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include "Socket.h"
|
||||||
|
|
||||||
|
int Socket::created_sockets=0;
|
||||||
|
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
SSL_CTX *Socket::ssl_ctx_server=NULL;
|
||||||
|
SSL_CTX *Socket::ssl_ctx_client=NULL;
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
|
extern Configfile cfg;
|
||||||
|
|
||||||
|
Socket::Socket():fd(-1)
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
,ssl_enabled(false),ssl(NULL)
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
{
|
||||||
|
if(!created_sockets)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
/* fuck windows, it needs this sh*t before allowing sockets to work */
|
||||||
|
WSADATA wsaData;
|
||||||
|
int wsa=WSAStartup(0xff,&wsaData);
|
||||||
|
if(wsa)
|
||||||
|
{
|
||||||
|
perror("Windows not working, call microsoft");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
#endif //WIN32
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
SSL_library_init();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
|
||||||
|
//initialize the context for both server and client operation
|
||||||
|
//server
|
||||||
|
ssl_ctx_server=NULL;
|
||||||
|
ssl_ctx_server=SSL_CTX_new(SSLv23_server_method());
|
||||||
|
/* create context */
|
||||||
|
if(!ssl_ctx_server)
|
||||||
|
throw Exception(_("Error creating SSL context"),__FILE__,__LINE__);
|
||||||
|
/* load certificate */
|
||||||
|
if(SSL_CTX_use_certificate_file(ssl_ctx_server,cfg.getCertificateFile().c_str(),SSL_FILETYPE_PEM)==-1)
|
||||||
|
throw Exception(_("Error loading certificate"),__FILE__,__LINE__);
|
||||||
|
/* load private key */
|
||||||
|
if(SSL_CTX_use_PrivateKey_file(ssl_ctx_server,cfg.getPrivateKeyFile().c_str(),SSL_FILETYPE_PEM)==-1)
|
||||||
|
throw Exception(_("Error loading private key"),__FILE__,__LINE__);
|
||||||
|
/* check that private key and cert match */
|
||||||
|
if(!SSL_CTX_check_private_key(ssl_ctx_server))
|
||||||
|
throw Exception(_("Private key doesn't match certificate file"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
//client
|
||||||
|
ssl_ctx_client=NULL;
|
||||||
|
ssl_ctx_client=SSL_CTX_new(SSLv23_client_method());
|
||||||
|
if(!ssl_ctx_client)
|
||||||
|
throw Exception(_("Error creating SSL context"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
//set options to make SSL_read and SSL_write behave more like read and write
|
||||||
|
SSL_CTX_set_mode(ssl_ctx_server,SSL_MODE_ENABLE_PARTIAL_WRITE); //PARTIAL_WRITE allows a write to suceed with fewer bytes sent
|
||||||
|
SSL_CTX_set_mode(ssl_ctx_client,SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||||
|
SSL_CTX_set_mode(ssl_ctx_server,SSL_MODE_AUTO_RETRY); //AUTO_RETRY will block SSL_read and SSL_write if a renegotiation is required
|
||||||
|
SSL_CTX_set_mode(ssl_ctx_client,SSL_MODE_AUTO_RETRY);
|
||||||
|
|
||||||
|
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
}
|
||||||
|
created_sockets++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* we have constructor and init because we may want to create the object
|
||||||
|
* and afterwards assign an fd obtained in other way with setFD
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void Socket::init()
|
||||||
|
{
|
||||||
|
// create socket ...
|
||||||
|
fd=socket(PF_INET,SOCK_STREAM,0);
|
||||||
|
//configure timeout
|
||||||
|
setTimeout(60,60);
|
||||||
|
|
||||||
|
if(fd==-1)
|
||||||
|
throw Exception(_(string("Error creating socket :")+Utils::inttostr(errno)+" "+Utils::errnotostrerror(errno)),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close the socket
|
||||||
|
*/
|
||||||
|
void Socket::close()
|
||||||
|
{
|
||||||
|
if(fd!=-1)
|
||||||
|
{
|
||||||
|
/* shutdown(fd,SHUT_RDWR); */ //we don't care about return, if we are on eof we are already closed and this doesn't hurt
|
||||||
|
//the same applies to close/closesocket
|
||||||
|
#ifndef WIN32
|
||||||
|
::close(fd);
|
||||||
|
#else
|
||||||
|
closesocket(fd);
|
||||||
|
#endif //WIN32
|
||||||
|
fd=-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* destructor.
|
||||||
|
*
|
||||||
|
* we close the socket if it's still open, and we
|
||||||
|
* decrement the created_sockets count
|
||||||
|
* when the count is 0, destroy the ssl contexts and,
|
||||||
|
* if on windows, do a WSACleanup
|
||||||
|
*/
|
||||||
|
Socket::~Socket()
|
||||||
|
{
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
if(ssl_enabled&&ssl!=NULL)
|
||||||
|
SSL_free(ssl);
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
close();
|
||||||
|
|
||||||
|
created_sockets--;
|
||||||
|
if(!created_sockets)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
WSACleanup();
|
||||||
|
#endif //WIN32
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
if(ssl_enabled&&ssl_ctx_server!=NULL)
|
||||||
|
SSL_CTX_free(ssl_ctx_server);
|
||||||
|
if(ssl_enabled&&ssl_ctx_client!=NULL)
|
||||||
|
SSL_CTX_free(ssl_ctx_client);
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
/**
|
||||||
|
* enable ssl on the socket
|
||||||
|
*
|
||||||
|
* @param server whether to enable server ssl or client ssl
|
||||||
|
*/
|
||||||
|
void Socket::enableSSL(bool server)
|
||||||
|
{
|
||||||
|
if(server)
|
||||||
|
ssl=SSL_new(ssl_ctx_server);
|
||||||
|
else
|
||||||
|
ssl=SSL_new(ssl_ctx_client);
|
||||||
|
if(ssl!=NULL)
|
||||||
|
{
|
||||||
|
SSL_set_fd(ssl,fd);
|
||||||
|
ssl_enabled=true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw Exception(_("Error creating ssl structure"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
if(server)
|
||||||
|
SSL_accept(ssl);
|
||||||
|
else
|
||||||
|
SSL_connect(ssl);
|
||||||
|
}
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
|
bool Socket::isClosed()
|
||||||
|
{
|
||||||
|
return -1==fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read lon bytes from the socket to buf buffer
|
||||||
|
*
|
||||||
|
* @param buf buffer to fill(must be previously reserved)
|
||||||
|
* @param lon number of bytes to read
|
||||||
|
* @return number of bytes read
|
||||||
|
*/
|
||||||
|
ssize_t Socket::readBytes(void *buf,ssize_t lon)
|
||||||
|
{
|
||||||
|
ssize_t retval=0; //shut compiler up
|
||||||
|
ssize_t readed=0;
|
||||||
|
|
||||||
|
while(readed<lon)
|
||||||
|
{
|
||||||
|
if(-1==fd)
|
||||||
|
throw Exception(_("Trying to read a non-opened or already closed socket"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
if(ssl_enabled)
|
||||||
|
retval=SSL_read(ssl,buf,lon);
|
||||||
|
else
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
retval=recv(fd,(char *)buf,lon,MSG_NOSIGNAL);
|
||||||
|
|
||||||
|
if(!retval)
|
||||||
|
throw NetworkException(_("Peer closed connection"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
if(retval<0)
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
if(ssl_enabled)
|
||||||
|
throw NetworkException(_("SSL error number: ")+Utils::inttostr(SSL_get_error(ssl,retval)),__FILE__,__LINE__);
|
||||||
|
else
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
throw NetworkException(_(Utils::errnotostrerror(errno)),__FILE__,__LINE__);
|
||||||
|
readed+=lon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read a single byte from the socket
|
||||||
|
*
|
||||||
|
* @return the read byte(char)
|
||||||
|
*/
|
||||||
|
char Socket::readByte()
|
||||||
|
{
|
||||||
|
char c=0;
|
||||||
|
|
||||||
|
readBytes(&c,1);
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read a full line and return a string with it
|
||||||
|
*
|
||||||
|
* @return the read string
|
||||||
|
*/
|
||||||
|
string Socket::readLine()
|
||||||
|
{
|
||||||
|
char c=0;
|
||||||
|
stringstream s;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
c=readByte();
|
||||||
|
|
||||||
|
if(c!=10&&c!=13&&c!=0)
|
||||||
|
s<<c;
|
||||||
|
}
|
||||||
|
while(c!=10&&!isClosed());
|
||||||
|
return s.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::writeBytes(void *bytes,ssize_t len)
|
||||||
|
{
|
||||||
|
int retval;
|
||||||
|
ssize_t written=0;
|
||||||
|
|
||||||
|
if(fd==-1)
|
||||||
|
throw Exception(_("Trying to write to a non-opened socket"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
while(written<len)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
if(ssl_enabled)
|
||||||
|
retval=SSL_write(ssl,bytes,len);
|
||||||
|
else
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
retval=send(fd,(char *)bytes,len,MSG_NOSIGNAL);
|
||||||
|
|
||||||
|
if(!retval)
|
||||||
|
throw NetworkException(_("Peer closed connection"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
if(retval<0)
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
if(ssl_enabled)
|
||||||
|
throw NetworkException(_("SSL error number: ")+Utils::inttostr(SSL_get_error(ssl,retval)),__FILE__,__LINE__);
|
||||||
|
else
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
throw NetworkException(_(Utils::errnotostrerror(errno)),__FILE__,__LINE__);
|
||||||
|
written+=len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::writeByte(char c)
|
||||||
|
{
|
||||||
|
writeBytes(&c,sizeof(char));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::writeLine(string s)
|
||||||
|
{
|
||||||
|
|
||||||
|
s+="\r\n";
|
||||||
|
|
||||||
|
writeBytes((void *)s.c_str(),s.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socket::setFD(int p_fd)
|
||||||
|
{
|
||||||
|
if(fd>0)
|
||||||
|
close();
|
||||||
|
|
||||||
|
if(p_fd>0)
|
||||||
|
{
|
||||||
|
fd=p_fd;
|
||||||
|
setTimeout(60,60);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw Exception(_("Error: fd not valid"),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns true if there's data waiting to be read on the socket
|
||||||
|
* if it's a serversocket means that a new connection is waiting
|
||||||
|
*
|
||||||
|
* @param unsigned int seconds to wait before returning true or false
|
||||||
|
*
|
||||||
|
* @return bool true if there's data(or a connection waiting) and false if there's not
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool Socket::canRead(float time)
|
||||||
|
{
|
||||||
|
fd_set rfd;
|
||||||
|
struct timeval timeout;
|
||||||
|
int seconds=0;
|
||||||
|
int useconds=0;
|
||||||
|
|
||||||
|
//calculate seconds and useconds
|
||||||
|
seconds=int(time);
|
||||||
|
useconds=int((time-seconds)*1000);
|
||||||
|
|
||||||
|
//set rfd to the fd of our connection
|
||||||
|
FD_ZERO(&rfd);
|
||||||
|
FD_SET(fd,&rfd);
|
||||||
|
|
||||||
|
//we wait x seconds for an incoming connection
|
||||||
|
timeout.tv_usec=useconds;
|
||||||
|
timeout.tv_sec=seconds;
|
||||||
|
if(select(fd+1,&rfd,NULL,NULL,&timeout)>0)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
if(ssl_enabled)
|
||||||
|
if(SSL_pending(ssl))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socket::connect(string host,unsigned int port)
|
||||||
|
{
|
||||||
|
struct sockaddr address;
|
||||||
|
struct sockaddr_in *inetaddress;
|
||||||
|
|
||||||
|
address=(sockaddr)Socket::resolve(host);
|
||||||
|
inetaddress=(sockaddr_in *)&address;
|
||||||
|
inetaddress->sin_port=htons(port);
|
||||||
|
|
||||||
|
int retval=::connect(fd,&address,sizeof(address));
|
||||||
|
if(retval==-1)
|
||||||
|
throw Exception(string(_("Error connecting to "))+host+":"+Utils::inttostr(port)+" "+string("(")+Socket::resolveToString(host)+string(")"),__FILE__,__LINE__);
|
||||||
|
if(!retval)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_GETADDRINFO
|
||||||
|
struct sockaddr Socket::resolve(string host)
|
||||||
|
{
|
||||||
|
struct addrinfo *hostinfo=NULL;
|
||||||
|
struct addrinfo hints;
|
||||||
|
struct sockaddr resolvedip;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
//configure hints to use IPv4 and IPv6, and resolve ips to name
|
||||||
|
memset(&hints,0,sizeof(hints));
|
||||||
|
hints.ai_flags=AI_ADDRCONFIG;
|
||||||
|
hints.ai_family=AF_UNSPEC;
|
||||||
|
hints.ai_socktype=SOCK_STREAM;
|
||||||
|
hints.ai_protocol=IPPROTO_TCP;
|
||||||
|
hints.ai_addrlen=0;
|
||||||
|
hints.ai_addr=0;
|
||||||
|
hints.ai_canonname=NULL;
|
||||||
|
|
||||||
|
error=getaddrinfo(host.c_str(),NULL,&hints,&hostinfo);
|
||||||
|
if(error)
|
||||||
|
#ifdef HAVE_GAI_STRERROR
|
||||||
|
throw Exception(gai_strerror(error),__FILE__,__LINE__);
|
||||||
|
#else
|
||||||
|
#ifdef WIN32
|
||||||
|
throw Exception("Winsock error "+Utils::inttostr(WSAGetLastError()),__FILE__,__LINE__);
|
||||||
|
#else
|
||||||
|
throw Exception("Socket error number "+Utils::inttostr(error),__FILE__,__LINE__);
|
||||||
|
#endif //WIN32
|
||||||
|
#endif //HAVE_GAI_STRERROR
|
||||||
|
|
||||||
|
resolvedip=*(hostinfo->ai_addr);
|
||||||
|
freeaddrinfo(hostinfo);
|
||||||
|
return resolvedip;
|
||||||
|
}
|
||||||
|
|
||||||
|
string Socket::resolveToString(string host)
|
||||||
|
{
|
||||||
|
struct sockaddr hostinfo;
|
||||||
|
struct sockaddr_in *inetip;
|
||||||
|
string strip;
|
||||||
|
|
||||||
|
hostinfo=Socket::resolve(host);
|
||||||
|
inetip=(sockaddr_in*)&hostinfo;
|
||||||
|
strip=string(inet_ntoa(inetip->sin_addr));
|
||||||
|
|
||||||
|
return strip;
|
||||||
|
}
|
||||||
|
|
||||||
|
string Socket::resolveInverselyToString(string ip)
|
||||||
|
{
|
||||||
|
int error;
|
||||||
|
char hostname[NI_MAXHOST];
|
||||||
|
struct sockaddr addr;
|
||||||
|
|
||||||
|
addr=resolve(ip);
|
||||||
|
error=getnameinfo(&addr,sizeof(struct sockaddr),hostname,NI_MAXHOST,NULL,0,NI_NAMEREQD);
|
||||||
|
|
||||||
|
if(error)
|
||||||
|
if(error==EAI_NONAME) //if the problem is that we didn't get a hostname, return empty string
|
||||||
|
hostname[0]='\0';
|
||||||
|
else
|
||||||
|
#ifdef HAVE_GAI_STRERROR
|
||||||
|
throw Exception(gai_strerror(error)+Utils::inttostr(error),__FILE__,__LINE__);
|
||||||
|
#else
|
||||||
|
#ifdef WIN32
|
||||||
|
throw Exception("Winsock error "+Utils::inttostr(WSAGetLastError()),__FILE__,__LINE__);
|
||||||
|
#else
|
||||||
|
throw Exception("Socket error number "+Utils::inttostr(error),__FILE__,__LINE__);
|
||||||
|
#endif //WIN32
|
||||||
|
#endif //HAVE_GAI_STRERROR
|
||||||
|
|
||||||
|
return string(hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WARNING!!! WARNING!!! WARNING!!!
|
||||||
|
* the following 3 functions are NOT thread-safe UNLESS used on a platform
|
||||||
|
* that is using thread-local storage (i.e. windows), so be VERY careful with
|
||||||
|
* them
|
||||||
|
*/
|
||||||
|
struct sockaddr Socket::resolve(string host)
|
||||||
|
{
|
||||||
|
struct sockaddr_in *addr_in;
|
||||||
|
struct sockaddr addr;
|
||||||
|
|
||||||
|
addr_in=(sockaddr_in *)&addr;
|
||||||
|
addr_in->sin_addr.s_addr=inet_addr(Socket::resolveToString(host).c_str());
|
||||||
|
addr_in->sin_family=AF_INET;
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
string Socket::resolveToString(string host)
|
||||||
|
{
|
||||||
|
struct hostent *hostinfo;
|
||||||
|
struct in_addr addr;
|
||||||
|
|
||||||
|
hostinfo=gethostbyname(host.c_str());
|
||||||
|
|
||||||
|
if(NULL==hostinfo)
|
||||||
|
throw Exception("Error resolving "+host,__FILE__,__LINE__);
|
||||||
|
|
||||||
|
memcpy(&addr,hostinfo->h_addr,sizeof(addr));
|
||||||
|
return string(inet_ntoa(addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
string Socket::resolveInverselyToString(string ip)
|
||||||
|
{
|
||||||
|
struct hostent *hostinfo;
|
||||||
|
unsigned long addr;
|
||||||
|
|
||||||
|
assert(ip.length()<16);
|
||||||
|
|
||||||
|
addr=inet_addr(ip.c_str());
|
||||||
|
hostinfo=gethostbyaddr((char *)&addr,4,AF_INET);
|
||||||
|
|
||||||
|
if(NULL==hostinfo)
|
||||||
|
throw Exception("Error resolving "+ip,__FILE__,__LINE__);
|
||||||
|
|
||||||
|
return string(hostinfo->h_name);
|
||||||
|
}
|
||||||
|
#endif //HAVE_GETADDRINFO
|
||||||
|
|
||||||
|
int Socket::getFD()
|
||||||
|
{
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this function sets timeouts for a socket on receive and send
|
||||||
|
* if either recv or send is -1, the timeout is not set, so for example
|
||||||
|
* if you want to set recv timeout but NOT send timeout, you could call
|
||||||
|
* this function like this:
|
||||||
|
* Socket::setTimeout(fd,3,-1); <-- set 3 seconds receive timeout and
|
||||||
|
* don't change send timeout
|
||||||
|
*
|
||||||
|
* setting timeout of either one to 0 disables the timeout, socket will
|
||||||
|
* block forever for data
|
||||||
|
*
|
||||||
|
* this function is needed because setting these timeouts is one of the
|
||||||
|
* less portable setsockopt functions, and lot's of operating systems
|
||||||
|
* do it differently
|
||||||
|
*
|
||||||
|
* @see setsockopt(2), socket(7) $LINUX_SRC/net/core/sock.c:{sock_setsockopt,sock_set_timeout}
|
||||||
|
*
|
||||||
|
* @todo check procedure on other operating systems like Solaris, *BSD and others
|
||||||
|
*
|
||||||
|
* @param recv timeout for receiving, a number in the format of 1.5 (one second and 500 milliseconds)
|
||||||
|
* @param send same as recv but for sending
|
||||||
|
*/
|
||||||
|
void Socket::setTimeout(float recv,float send)
|
||||||
|
{
|
||||||
|
if(fd<=0)
|
||||||
|
throw Exception("Socket invalid: "+Utils::inttostr(fd)+" ",__FILE__,__LINE__);
|
||||||
|
#ifdef WIN32
|
||||||
|
//set timeout for receiving
|
||||||
|
if(recv)
|
||||||
|
{
|
||||||
|
unsigned timeout=int(recv*1000);
|
||||||
|
if(-1==setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(timeout)))
|
||||||
|
throw Exception(Utils::errnotostrerror(errno),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
//set timeout for sending
|
||||||
|
if(send)
|
||||||
|
{
|
||||||
|
unsigned timeout=int(send*1000);
|
||||||
|
if(-1==setsockopt(fd,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(timeout)))
|
||||||
|
throw Exception(Utils::errnotostrerror(errno),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
struct timeval timeout;
|
||||||
|
int seconds=0;
|
||||||
|
int useconds=0;
|
||||||
|
|
||||||
|
//calculate seconds and useconds
|
||||||
|
if(recv)
|
||||||
|
{
|
||||||
|
seconds=int(recv);
|
||||||
|
useconds=int((recv-seconds)*1000);
|
||||||
|
timeout.tv_usec=useconds;
|
||||||
|
timeout.tv_sec=seconds;
|
||||||
|
if(-1==setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(timeout)))
|
||||||
|
throw Exception(Utils::errnotostrerror(errno),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(send)
|
||||||
|
{
|
||||||
|
seconds=int(send);
|
||||||
|
useconds=int((send-seconds)*1000);
|
||||||
|
timeout.tv_usec=useconds;
|
||||||
|
timeout.tv_sec=seconds;
|
||||||
|
if(-1==setsockopt(fd,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(timeout)))
|
||||||
|
throw Exception(Utils::errnotostrerror(errno),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
103
src/Socket.h
Normal file
103
src/Socket.h
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef SOCKET_H
|
||||||
|
#define SOCKET_H
|
||||||
|
|
||||||
|
#include "hermes.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#ifdef WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#ifndef AI_ADDRCONFIG
|
||||||
|
#define AI_ADDRCONFIG 0 //if the windows we are compiling at is old, define it as 0
|
||||||
|
#endif //AI_ADDRCONFIG
|
||||||
|
#else
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#endif //WIN32
|
||||||
|
#include <errno.h>
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
|
//this is a bit of a hack
|
||||||
|
//if a system doesn't have MSG_NOSIGNAL then we define it as 0 (that is, no options selected)
|
||||||
|
//I've tried this on Solaris and NetBSD and it seems to work. Still, recheck in the future (TODO)
|
||||||
|
#ifndef MSG_NOSIGNAL
|
||||||
|
#define MSG_NOSIGNAL 0
|
||||||
|
#endif //MSG_NOSIGNAL
|
||||||
|
|
||||||
|
#include "Configfile.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* implements a socket class independent of operating system and that supports ssl
|
||||||
|
*/
|
||||||
|
class Socket
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
int fd;
|
||||||
|
private:
|
||||||
|
// bool closed;
|
||||||
|
static int created_sockets;
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
bool ssl_enabled;
|
||||||
|
SSL *ssl;
|
||||||
|
static SSL_CTX *ssl_ctx_client;
|
||||||
|
static SSL_CTX *ssl_ctx_server;
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
public:
|
||||||
|
Socket();
|
||||||
|
~Socket();
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
void enableSSL(bool);
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
void setFD(int);
|
||||||
|
bool canRead(float);
|
||||||
|
bool connect(string,unsigned int);
|
||||||
|
int getFD();
|
||||||
|
static struct sockaddr resolve(string);
|
||||||
|
static string resolveToString(string);
|
||||||
|
static string resolveInverselyToString(string);
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void close();
|
||||||
|
//reading and writing
|
||||||
|
char readByte();
|
||||||
|
ssize_t readBytes(void *,ssize_t);
|
||||||
|
string readLine();
|
||||||
|
|
||||||
|
void writeByte(char);
|
||||||
|
void writeBytes(void *,ssize_t);
|
||||||
|
void writeLine(string);
|
||||||
|
|
||||||
|
bool isClosed();
|
||||||
|
|
||||||
|
void setTimeout(float,float);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //SOCKET_H
|
99
src/Spf.cpp
Normal file
99
src/Spf.cpp
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include "Spf.h"
|
||||||
|
|
||||||
|
SPF_server_t *Spf::spfserver=NULL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* constructor
|
||||||
|
*
|
||||||
|
* it will create a spfserver if this is the first created object of the class.
|
||||||
|
* if it isn't, then we just create an spfrequest
|
||||||
|
*/
|
||||||
|
Spf::Spf():spfrequest(NULL),spfresponse(NULL)
|
||||||
|
{
|
||||||
|
pthread_mutex_init(&mutex,NULL);
|
||||||
|
if(NULL==spfserver)
|
||||||
|
if(NULL==(spfserver=SPF_server_new(SPF_DNS_CACHE,0)))
|
||||||
|
throw Exception(_("Can't initialize SPF library"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
if(NULL==(spfrequest=SPF_request_new(spfserver)))
|
||||||
|
throw Exception(_("Can't initialize SPF request"),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* destructor
|
||||||
|
*
|
||||||
|
* frees the memory of the spfrequest
|
||||||
|
*/
|
||||||
|
Spf::~Spf()
|
||||||
|
{
|
||||||
|
pthread_mutex_destroy(&mutex);
|
||||||
|
if(NULL!=spfrequest) SPF_request_free(spfrequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* frees all memory related to the spf class
|
||||||
|
*
|
||||||
|
* this is needed because the common things are only initialized
|
||||||
|
* once (and are static), and when we close the program we need
|
||||||
|
* to deinitialize them
|
||||||
|
*/
|
||||||
|
void Spf::deinitialize()
|
||||||
|
{
|
||||||
|
if(NULL!=spfserver)
|
||||||
|
SPF_server_free(spfserver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make a query to the dns system for an spf record
|
||||||
|
*
|
||||||
|
* highly inspired from fakehermes' source
|
||||||
|
*
|
||||||
|
* @param ip the ip of the remote server
|
||||||
|
* @param helo the hello string of the remote server
|
||||||
|
* @param from the envelope from address
|
||||||
|
*
|
||||||
|
* @returns true if it is not incorrect
|
||||||
|
*/
|
||||||
|
bool Spf::query(string ip,string helo,string from)
|
||||||
|
{
|
||||||
|
bool retval=false;
|
||||||
|
|
||||||
|
if(SPF_request_set_ipv4_str(spfrequest,ip.c_str()))
|
||||||
|
throw Exception(_("Error configuring IP for SPF request"),__FILE__,__LINE__);
|
||||||
|
if(SPF_request_set_helo_dom(spfrequest,helo.c_str()))
|
||||||
|
throw Exception(_("Error configuring HELO for SPF request"),__FILE__,__LINE__);
|
||||||
|
if(SPF_request_set_env_from(spfrequest,from.c_str()))
|
||||||
|
throw Exception(_("Error configuring FROM for SPF request"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
//make the actual query
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
SPF_request_query_mailfrom(spfrequest,&spfresponse);
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
|
||||||
|
if(NULL!=spfresponse)
|
||||||
|
{
|
||||||
|
retval=(SPF_RESULT_FAIL==SPF_response_result(spfresponse)||SPF_RESULT_SOFTFAIL==SPF_response_result(spfresponse))?false:true;
|
||||||
|
SPF_response_free(spfresponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
44
src/Spf.h
Normal file
44
src/Spf.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef SPF_H
|
||||||
|
#define SPF_H
|
||||||
|
|
||||||
|
#include "hermes.h"
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#include <spf2/spf.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
class Spf
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static SPF_server_t *spfserver;
|
||||||
|
SPF_request_t *spfrequest;
|
||||||
|
SPF_response_t *spfresponse;
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
public:
|
||||||
|
Spf();
|
||||||
|
~Spf();
|
||||||
|
static void deinitialize();
|
||||||
|
bool query(string,string,string);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //SPF_H
|
35
src/UnixLogger.cpp
Normal file
35
src/UnixLogger.cpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include "UnixLogger.h"
|
||||||
|
|
||||||
|
UnixLogger::UnixLogger()
|
||||||
|
{
|
||||||
|
openlog("hermes",LOG_NDELAY,LOG_MAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
UnixLogger::~UnixLogger()
|
||||||
|
{
|
||||||
|
closelog();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnixLogger::addMessage(int loglevel,string logmessage)
|
||||||
|
{
|
||||||
|
syslog(loglevel,logmessage.c_str());
|
||||||
|
}
|
43
src/UnixLogger.h
Normal file
43
src/UnixLogger.h
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef UNIXLOGGER_H
|
||||||
|
#define UNIXLOGGER_H
|
||||||
|
|
||||||
|
#include <syslog.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* implements the logger for Linux/UNIX
|
||||||
|
*
|
||||||
|
* @see Logger
|
||||||
|
*/
|
||||||
|
class UnixLogger: public Logger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UnixLogger();
|
||||||
|
~UnixLogger();
|
||||||
|
void addMessage(int,string);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //UNIXLOGGER_H
|
580
src/Utils.cpp
Normal file
580
src/Utils.cpp
Normal file
|
@ -0,0 +1,580 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
|
extern Configfile cfg;
|
||||||
|
|
||||||
|
//--------------------
|
||||||
|
// string functions:
|
||||||
|
//--------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert integer to string
|
||||||
|
*
|
||||||
|
* @param integer integer to convert
|
||||||
|
*
|
||||||
|
* @return string the integer converted to stringtype
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
string Utils::inttostr(int integer)
|
||||||
|
{
|
||||||
|
stringstream s;
|
||||||
|
s << integer;
|
||||||
|
return s.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return lowercase version of s
|
||||||
|
*
|
||||||
|
* @param s string to convert
|
||||||
|
*
|
||||||
|
* @return string lowercase version of s
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
string Utils::strtolower(string s)
|
||||||
|
{
|
||||||
|
for(unsigned int i=0;i<s.length();i++)
|
||||||
|
s[i]=tolower(s[i]);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* trim spaces from both sides of the string
|
||||||
|
*
|
||||||
|
* @param s string to trim
|
||||||
|
*
|
||||||
|
* @return string trimmed string
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
string Utils::trim(string s)
|
||||||
|
{
|
||||||
|
while(isspace(s[0]))
|
||||||
|
s.erase(0,1);
|
||||||
|
|
||||||
|
while(isspace(s[s.length()-1]))
|
||||||
|
s.erase(s.length()-1,1);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------
|
||||||
|
// email-related functions:
|
||||||
|
//------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* decide whether a triplet should be greylisted or not
|
||||||
|
*
|
||||||
|
* basically it follows this diagram:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* +------------------------------------------+
|
||||||
|
* | |yes
|
||||||
|
* | whitelisted?(IP or TO or DOMAIN or HOST) |----> don't greylist
|
||||||
|
* | |
|
||||||
|
* +------------------------------------------+
|
||||||
|
* |
|
||||||
|
* | no
|
||||||
|
* |
|
||||||
|
* v
|
||||||
|
* +----------------------------------+
|
||||||
|
* | |yes
|
||||||
|
* | blacklisted? (IP or FROM) |----> greylist
|
||||||
|
* | |
|
||||||
|
* +----------------------------------+
|
||||||
|
* |
|
||||||
|
* | no
|
||||||
|
* |
|
||||||
|
* v
|
||||||
|
* +----------------------------------+
|
||||||
|
* | |yes
|
||||||
|
* | greylisted? (triplet) |----> greylist
|
||||||
|
* | |
|
||||||
|
* +----------------------------------+
|
||||||
|
* |
|
||||||
|
* | no
|
||||||
|
* |
|
||||||
|
* v
|
||||||
|
* don't greylist
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param dbfile sqlite database file to use. if doesn't exist then initialize
|
||||||
|
* @param ip ip of remote client, first of our triplet
|
||||||
|
* @param p_from mail from header, second of our triplet
|
||||||
|
* @param p_to rcpt to header, third of our triplet
|
||||||
|
*
|
||||||
|
* @return whether triplet should get greylisted or not
|
||||||
|
* @todo unify {white,black,grey}list in one function that returns a different constant in each case
|
||||||
|
*/
|
||||||
|
bool Utils::greylist(string dbfile,string& ip,string& p_from,string& p_to)
|
||||||
|
{
|
||||||
|
string from=Database::cleanString(p_from);
|
||||||
|
string to=Database::cleanString(p_to);
|
||||||
|
string hostname;
|
||||||
|
Database db;
|
||||||
|
|
||||||
|
db.setDatabaseFile(dbfile);
|
||||||
|
db.open();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
hostname=Socket::resolveInverselyToString(ip);
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
hostname="unresolved";
|
||||||
|
}
|
||||||
|
if(db.whitelistedIP(ip)||db.whitelistedTO(to)||db.whitelistedDomain(getdomain(to))||(""!=hostname&&db.whitelistedHostname(hostname)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(db.blacklistedIP(ip)||db.blacklistedFROM(from)||db.blacklistedTO(to)||db.blacklistedToDomain(getdomain(to)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if(db.greylisted(ip,from,to,cfg.getInitialExpiry(),cfg.getInitialBlacklist(),cfg.getWhitelistExpiry()))
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whether an ip is listed on the database as whitelisted
|
||||||
|
*
|
||||||
|
* @param dbfile sqlite3 database file
|
||||||
|
* @param ip ip of remote machine
|
||||||
|
*
|
||||||
|
* @return whether ip is whitelisted or not
|
||||||
|
* @todo unify {white,black,grey}list in one function that returns a different constant in each case
|
||||||
|
*/
|
||||||
|
bool Utils::whitelisted(string dbfile,string& ip)
|
||||||
|
{
|
||||||
|
Database db;
|
||||||
|
string hostname;
|
||||||
|
|
||||||
|
db.setDatabaseFile(dbfile);
|
||||||
|
db.open();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
hostname=Socket::resolveInverselyToString(ip);
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
hostname="unresolved";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(db.whitelistedIP(ip)||(""!=hostname&&db.whitelistedHostname(hostname)))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whether an ip is listed on the database as blacklisted
|
||||||
|
*
|
||||||
|
* @param dbfile sqlite3 database file
|
||||||
|
* @param ip ip of remote machine
|
||||||
|
*
|
||||||
|
* @return whether ip is whitelisted or not
|
||||||
|
* @todo this should contain all cases when we should reject a connection
|
||||||
|
*/
|
||||||
|
bool Utils::blacklisted(string dbfile,string& ip,string& to)
|
||||||
|
{
|
||||||
|
Database db;
|
||||||
|
string hostname;
|
||||||
|
|
||||||
|
db.setDatabaseFile(dbfile);
|
||||||
|
db.open();
|
||||||
|
|
||||||
|
return false==db.allowedDomainPerIP(getdomain(to),ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this function extracts the email from rcpt to and mail from lines
|
||||||
|
* i.e.:
|
||||||
|
* rcpt to: "BillG" <bill@m.com> --> bill@m.com
|
||||||
|
*
|
||||||
|
* @param rawline line read from the socket
|
||||||
|
*
|
||||||
|
* @return email extracted from rawline
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
string Utils::getmail(string& rawline)
|
||||||
|
{
|
||||||
|
string email;
|
||||||
|
string::size_type start=0,end=0;
|
||||||
|
|
||||||
|
start=rawline.find('@');
|
||||||
|
if(start!=string::npos) //case 1 -> email has an @(most common), start from there and look for spaces or </> as delimiters
|
||||||
|
{
|
||||||
|
start=rawline.find_last_of(":< ",start);
|
||||||
|
end=rawline.find_first_of("> ",start+1);
|
||||||
|
if(end==string::npos) //there is no space at the end, from start to end-of-line
|
||||||
|
end=rawline.length();
|
||||||
|
if(start!=string::npos&&end!=string::npos&&start<=end)
|
||||||
|
return trim(rawline.substr(start+1,end-start-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
start=rawline.find(':');
|
||||||
|
start=rawline.find_first_of("< ",start);
|
||||||
|
if(start!=string::npos) // case 2 -> email is between <> or spaces and doesn't contain an @(i.e. local user)
|
||||||
|
{
|
||||||
|
start=rawline.find_first_not_of("< ",start+1);
|
||||||
|
if('"'==rawline[start]) // it can contain a name for the from(rcpt to: "BillG" billg@m.com)
|
||||||
|
{
|
||||||
|
start=rawline.find("\"",start+1);
|
||||||
|
start=rawline.find_first_not_of("< ",start+1);
|
||||||
|
}
|
||||||
|
end=rawline.find_first_of("> ",start+1);
|
||||||
|
if(start!=string::npos&&end!=string::npos&&start<=end)
|
||||||
|
return trim(rawline.substr(start,end-start));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""; //if there's a problem just return an empty string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this functions returns the domain part of email
|
||||||
|
* bill@m.com --> m.com
|
||||||
|
* it's mainly useful to whitelist destination domains, if
|
||||||
|
* for example a customer doesn't want any of it's emails using greylisting
|
||||||
|
* in contrast to only wanting _some_ specific email
|
||||||
|
*
|
||||||
|
* @param email email to get domain from
|
||||||
|
*
|
||||||
|
* @return domain of email
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
string Utils::getdomain(string& email)
|
||||||
|
{
|
||||||
|
if(email.rfind('@'))
|
||||||
|
return trim(email.substr(email.rfind('@')+1));
|
||||||
|
else
|
||||||
|
return string("");
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
|
||||||
|
//-----------------
|
||||||
|
// posix-functions:
|
||||||
|
//-----------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get uid from user
|
||||||
|
*
|
||||||
|
* keep in mind that this function is not thread-safe, because
|
||||||
|
* getpwnam isn't, and we don't actually care, this function
|
||||||
|
* gets called BEFORE we start spawning threads, but you should
|
||||||
|
* keep this details in mind if you intend to use this outside hermes
|
||||||
|
*
|
||||||
|
* @param user user to query
|
||||||
|
*
|
||||||
|
* @return uid for user
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int Utils::usertouid(string user)
|
||||||
|
{
|
||||||
|
struct passwd *pwd;
|
||||||
|
pwd=getpwnam(user.c_str());
|
||||||
|
if(NULL==pwd)
|
||||||
|
throw Exception(_("Error reading user data. user doesn't exist?"),__FILE__,__LINE__);
|
||||||
|
return pwd->pw_uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get gid from groupname
|
||||||
|
*
|
||||||
|
* IMPORTANT:
|
||||||
|
* keep in mind that this function is not thread-safe, because
|
||||||
|
* getgrnam isn't, and we don't actually care, this function
|
||||||
|
* gets called BEFORE we start spawning threads, but you should
|
||||||
|
* keep this details in mind if you intend to use this outside hermes
|
||||||
|
*
|
||||||
|
* @param groupname groupname to query
|
||||||
|
*
|
||||||
|
* @return gid for groupname
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int Utils::grouptogid(string groupname)
|
||||||
|
{
|
||||||
|
struct group *grp;
|
||||||
|
grp=getgrnam(groupname.c_str());
|
||||||
|
if(NULL==grp)
|
||||||
|
throw Exception(_("Error reading group data. group doesn't exist?"),__FILE__,__LINE__);
|
||||||
|
return grp->gr_gid;
|
||||||
|
}
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
// misc functions:
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whether a file is accesible by current process/user
|
||||||
|
*
|
||||||
|
* @param file file to check
|
||||||
|
*
|
||||||
|
* @return is file readable?
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool Utils::file_exists(string file)
|
||||||
|
{
|
||||||
|
FILE *f=fopen(file.c_str(),"r");
|
||||||
|
if(NULL==f)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fclose(f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whether a directory is accesible by current process/user
|
||||||
|
*
|
||||||
|
* @param string directory to check
|
||||||
|
*
|
||||||
|
* @return isdir readable?
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
bool Utils::dir_exists(string dir)
|
||||||
|
{
|
||||||
|
DIR *d=opendir(dir.c_str());
|
||||||
|
if(NULL==d)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
closedir(d);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the error string corresponding to errnum
|
||||||
|
*
|
||||||
|
* @param errnum errno to get description for
|
||||||
|
*
|
||||||
|
* @return description of error
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
string Utils::errnotostrerror(int errnum)
|
||||||
|
{
|
||||||
|
char buf[2048]="";
|
||||||
|
char *strerr;
|
||||||
|
// if(strerror_r(errnum,strerr,1024)!=-1)
|
||||||
|
#ifndef WIN32
|
||||||
|
strerr=strerror_r(errnum,buf,2048);
|
||||||
|
#else
|
||||||
|
strerr="Error ";
|
||||||
|
#endif //WIN32
|
||||||
|
return string(strerr)+" ("+inttostr(errnum)+")";
|
||||||
|
// else
|
||||||
|
// return string("Error "+inttostr(errno)+" retrieving error code for error number ")+inttostr(errnum);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns whether the ip is in a dns list or not
|
||||||
|
*
|
||||||
|
* to check a dns list, you just append at the begining of the
|
||||||
|
* dns string the inversed ip and then resolve it. for example,
|
||||||
|
* to check the ip 1.2.3.4 you just resolve the following
|
||||||
|
* hostname:
|
||||||
|
* 4.3.2.1.zen.spamhaus.org
|
||||||
|
*
|
||||||
|
* if it returns an ip, then it is listed, else it isn't.
|
||||||
|
*
|
||||||
|
* since 1.4 this doesn't check a single domain but lists of them. we also added a percentage to decide when
|
||||||
|
* it's listed or not.
|
||||||
|
* So for example, if you want to check if you are on zen.spamhaus.org AND on dnsbl.sorbs.net, you have two options:
|
||||||
|
* - you need your ip to be listed on both domains to be considered as listed (100% of the lists)
|
||||||
|
* - you need your ip to be listed on either of them (50% of the lists)
|
||||||
|
*
|
||||||
|
* @param dns_domains list of dns domains to check
|
||||||
|
* @param percentage percentage of domains that have to list the ip to be considered as "listed"
|
||||||
|
* @param ip ip to check
|
||||||
|
*
|
||||||
|
* @return whether ip is blacklisted or not
|
||||||
|
*/
|
||||||
|
bool Utils::listed_on_dns_lists(list<string>& dns_domains,unsigned char percentage,string& ip)
|
||||||
|
{
|
||||||
|
string reversedip;
|
||||||
|
unsigned char number_of_lists=dns_domains.size();
|
||||||
|
unsigned char times_listed=0;
|
||||||
|
unsigned char checked_lists=0;
|
||||||
|
|
||||||
|
reversedip=reverseip(ip);
|
||||||
|
|
||||||
|
for(list<string>::iterator i=dns_domains.begin();i!=dns_domains.end();i++)
|
||||||
|
{
|
||||||
|
string dns_domain;
|
||||||
|
|
||||||
|
dns_domain=*i;
|
||||||
|
//add a dot if domain doesn't include it
|
||||||
|
if('.'!=dns_domain[0])
|
||||||
|
dns_domain.insert(0,".");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Socket::resolve(reversedip+dns_domain);
|
||||||
|
times_listed++;
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << ip << " listed on " << dns_domain << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << ip << " NOT listed on " << dns_domain << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
|
||||||
|
checked_lists++;
|
||||||
|
|
||||||
|
if((times_listed*100/number_of_lists)>=percentage)
|
||||||
|
return true;
|
||||||
|
//if we have checked a number of lists that make it impossible for this function
|
||||||
|
//to return true, then return false
|
||||||
|
if((checked_lists*100/number_of_lists)>100-percentage)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reverse an ip string from 1.2.3.4 to 4.3.2.1
|
||||||
|
*
|
||||||
|
* @param ip ip to reverse
|
||||||
|
*
|
||||||
|
* @return the reversed ip
|
||||||
|
*/
|
||||||
|
string Utils::reverseip(string& ip)
|
||||||
|
{
|
||||||
|
string inverseip="";
|
||||||
|
string::size_type pos=0,ppos=0;
|
||||||
|
|
||||||
|
//find first digit
|
||||||
|
pos=ip.rfind('.',ip.length());
|
||||||
|
if(string::npos==pos)
|
||||||
|
throw Exception(ip+" is not an IP",__FILE__,__LINE__);
|
||||||
|
else
|
||||||
|
inverseip=ip.substr(pos+1);
|
||||||
|
|
||||||
|
//find second digit
|
||||||
|
ppos=pos;
|
||||||
|
pos=ip.rfind('.',ppos-1);
|
||||||
|
if(string::npos==pos)
|
||||||
|
throw Exception(ip+" is not an IP",__FILE__,__LINE__);
|
||||||
|
else
|
||||||
|
inverseip+="."+ip.substr(pos+1,ppos-pos-1);
|
||||||
|
|
||||||
|
//find third digit
|
||||||
|
ppos=pos;
|
||||||
|
pos=ip.rfind('.',ppos-1);
|
||||||
|
if(string::npos==pos)
|
||||||
|
throw Exception(ip+" is not an IP",__FILE__,__LINE__);
|
||||||
|
else
|
||||||
|
inverseip+="."+ip.substr(pos+1,ppos-pos-1);
|
||||||
|
|
||||||
|
//append fourth digit
|
||||||
|
inverseip+="."+ip.substr(0,pos);
|
||||||
|
|
||||||
|
return inverseip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the time formatted according to rfc2821 and rfc2822
|
||||||
|
*
|
||||||
|
* @param timestamp time from which to return string. if null, it equals time(NULL)
|
||||||
|
*
|
||||||
|
* @return a formatted string of the form 18 May 2007 11:01:52 -0000
|
||||||
|
*/
|
||||||
|
string Utils::rfc2821_date(time_t *timestamp)
|
||||||
|
{
|
||||||
|
time_t utctime;
|
||||||
|
char buf[100]; //TODO:100 bytes should be enough, check though
|
||||||
|
char tzbuf[6]; //max length of a tz string is 5 (i.e. -0800)
|
||||||
|
struct tm local_time;
|
||||||
|
struct tm *p_local_time;
|
||||||
|
|
||||||
|
p_local_time=&local_time;
|
||||||
|
|
||||||
|
if(NULL==timestamp)
|
||||||
|
utctime=time(NULL);
|
||||||
|
else
|
||||||
|
utctime=*timestamp;
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
if(NULL==(p_local_time=localtime(&utctime)))
|
||||||
|
#else
|
||||||
|
if(NULL==(localtime_r(&utctime,&local_time)))
|
||||||
|
#endif //WIN32
|
||||||
|
throw Exception(_("Error converting date"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
if(0==strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S ",p_local_time))
|
||||||
|
throw Exception(_("Error formatting date"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
//win32 doesn't return the same for %z on strftime, so do it manually
|
||||||
|
_tzset();
|
||||||
|
snprintf(tzbuf,sizeof(tzbuf),"%c%02d%02d",(-_timezone<0)?'-':'+',abs((_timezone/60)/60),abs((_timezone/60)%60));
|
||||||
|
#else
|
||||||
|
if(0==strftime(tzbuf,sizeof(tzbuf),"%z",p_local_time))
|
||||||
|
throw Exception(_("Error formatting timezone"),__FILE__,__LINE__);
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
return string(buf)+string(tzbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
//hack for machines not defining HOST_NAME_MAX
|
||||||
|
//according to docs, if it's not defined, it SHOULD be 255
|
||||||
|
#ifndef HOST_NAME_MAX
|
||||||
|
#define HOST_NAME_MAX 255
|
||||||
|
#endif //HOST_NAME_MAX
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the hostname of the computer we are running at
|
||||||
|
*
|
||||||
|
* since this will be done for each message sent, and hostname shouldn't change during
|
||||||
|
* the execution of hermes, we will simply get it the first time and then we will store
|
||||||
|
* it on a static variable
|
||||||
|
*
|
||||||
|
* added the option to set the hostname on the configfile. should be useful on windows,
|
||||||
|
* where gethostname only returns the host part
|
||||||
|
*
|
||||||
|
* @return the hostname of the computer
|
||||||
|
*/
|
||||||
|
string Utils::gethostname()
|
||||||
|
{
|
||||||
|
static char buf[HOST_NAME_MAX]={0};
|
||||||
|
|
||||||
|
if('\0'==buf[0])
|
||||||
|
{
|
||||||
|
if(cfg.getHostname()!="")
|
||||||
|
strncpy(buf,cfg.getHostname().c_str(),HOST_NAME_MAX);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(-1==::gethostname(buf,HOST_NAME_MAX))
|
||||||
|
throw Exception("Error getting current hostname"+Utils::errnotostrerror(errno),__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(buf);
|
||||||
|
}
|
84
src/Utils.h
Normal file
84
src/Utils.h
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef UTILS_H
|
||||||
|
#define UTILS_H
|
||||||
|
|
||||||
|
#include "hermes.h"
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <grp.h>
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
#include "Database.h"
|
||||||
|
#include "Socket.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
#define sleep(x) Sleep(1000*(x))
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
/*#ifndef MAX
|
||||||
|
#define MAX(x,y) (((x)>(y))?(x):(y))
|
||||||
|
#endif //MAX*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this class implements common utilities
|
||||||
|
*/
|
||||||
|
class Utils
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
//string utilities
|
||||||
|
static string strtolower(string);
|
||||||
|
static string trim(string);
|
||||||
|
static string inttostr(int);
|
||||||
|
|
||||||
|
//email-related utilities
|
||||||
|
static string getmail(string&);
|
||||||
|
static string getdomain(string&);
|
||||||
|
static string reverseip(string&);
|
||||||
|
|
||||||
|
//spam-related utilities (TODO: move to a different class)
|
||||||
|
static bool greylist(string,string&,string&,string&);
|
||||||
|
static bool listed_on_dns_lists(list<string>&,unsigned char,string&);
|
||||||
|
static bool whitelisted(string,string&);
|
||||||
|
static bool blacklisted(string,string&,string&);
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
//posix-utils
|
||||||
|
static int usertouid(string);
|
||||||
|
static int grouptogid(string);
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
//misc
|
||||||
|
static bool file_exists(string);
|
||||||
|
static bool dir_exists(string);
|
||||||
|
static string errnotostrerror(int);
|
||||||
|
static string rfc2821_date(time_t *timestamp=NULL);
|
||||||
|
static string gethostname();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //UTILS_H
|
413
src/hermes.cpp
Normal file
413
src/hermes.cpp
Normal file
|
@ -0,0 +1,413 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include <iostream>
|
||||||
|
#include <list>
|
||||||
|
#include <stack>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <time.h>
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
#include <openssl/crypto.h>
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
#ifndef WIN32
|
||||||
|
#include <grp.h>
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
#include "Proxy.h"
|
||||||
|
#include "Socket.h"
|
||||||
|
#include "ServerSocket.h"
|
||||||
|
#include "Configfile.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
void *thread_main(void *);
|
||||||
|
void *cleaner_thread_run(void *);
|
||||||
|
void exit_requested(int);
|
||||||
|
void write_pid(string,pid_t);
|
||||||
|
|
||||||
|
//global var to know when we have to exit
|
||||||
|
bool quit=false;
|
||||||
|
|
||||||
|
//mutexes
|
||||||
|
pthread_mutex_t childrenlist_mutex=PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
pthread_mutex_t info_stack_mutex=PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
//our config
|
||||||
|
Configfile cfg;
|
||||||
|
|
||||||
|
//our logger
|
||||||
|
LOGGER_CLASS hermes_log;
|
||||||
|
|
||||||
|
list<pthread_t> children;
|
||||||
|
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
pthread_mutex_t ssl_locks[CRYPTO_NUM_LOCKS]={PTHREAD_MUTEX_INITIALIZER};
|
||||||
|
|
||||||
|
void ssl_locking_function(int mode,int n,const char *file,int line)
|
||||||
|
{
|
||||||
|
if(n>CRYPTO_NUM_LOCKS)
|
||||||
|
throw Exception(_("Error, "+Utils::inttostr(n)+" is bigger than CRYPTO_NUM_LOCKS("+Utils::inttostr(CRYPTO_NUM_LOCKS)+")"),__FILE__,__LINE__);
|
||||||
|
if(mode&CRYPTO_LOCK)
|
||||||
|
pthread_mutex_lock(&ssl_locks[n]);
|
||||||
|
else
|
||||||
|
pthread_mutex_unlock(&ssl_locks[n]);
|
||||||
|
}
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
|
||||||
|
int
|
||||||
|
#ifdef WIN32_SERVICE
|
||||||
|
hermes_main
|
||||||
|
#else
|
||||||
|
main
|
||||||
|
#endif //WIN32_SERVICE
|
||||||
|
(int argc,char *argv[])
|
||||||
|
{
|
||||||
|
/* TODO:think of this again
|
||||||
|
if(argc>2)
|
||||||
|
{
|
||||||
|
for(unsigned i=1;i<argc;i++)
|
||||||
|
{
|
||||||
|
argv++
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
CRYPTO_set_locking_callback(ssl_locking_function);
|
||||||
|
CRYPTO_set_id_callback(pthread_self);
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(2==argc)
|
||||||
|
{
|
||||||
|
if(!Utils::file_exists(argv[1]))
|
||||||
|
throw Exception(string(_("Config file "))+argv[1]+_(" doesn't exist or is not readable."),__FILE__,__LINE__);
|
||||||
|
cfg.parse(argv[1]);
|
||||||
|
}
|
||||||
|
cfg.validateConfig();
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
cout << string(e) << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
unsigned long nconns=0;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
|
||||||
|
signal(SIGTERM,exit_requested);
|
||||||
|
signal(SIGINT,exit_requested);
|
||||||
|
|
||||||
|
//we have to create the server socket BEFORE chrooting, because if we don't,
|
||||||
|
//SSL cannot initialize because it's missing libz
|
||||||
|
ServerSocket server;
|
||||||
|
pthread_t cleaner_thread;
|
||||||
|
string peer_address;
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
if(cfg.getBackground())
|
||||||
|
{
|
||||||
|
int retval;
|
||||||
|
|
||||||
|
retval=fork();
|
||||||
|
if(retval>0)
|
||||||
|
exit(0); //succesful fork
|
||||||
|
|
||||||
|
if(retval<0)
|
||||||
|
{
|
||||||
|
cout << _("Error forking into the background") << Utils::errnotostrerror(errno) << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cfg.getPidFile()!="")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
write_pid(cfg.getPidFile(),getpid());
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
hermes_log.addMessage(LOG_ERR,e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cfg.getChroot()!="")
|
||||||
|
{
|
||||||
|
//this is needed to get hermes to load the dns resolver BEFORE chrooting
|
||||||
|
(void)gethostbyname("iteisa.com");
|
||||||
|
chdir(cfg.getChroot().c_str());
|
||||||
|
if(-1==chroot(cfg.getChroot().c_str()))
|
||||||
|
{
|
||||||
|
cout << _("Couldn't chroot ") << Utils::errnotostrerror(errno) << endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
chdir("/");
|
||||||
|
}
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
server.init();
|
||||||
|
server.setPort(cfg.getListeningPort());
|
||||||
|
server.listen(cfg.getListeningPort(),cfg.getBindTo());
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
cout << e << endl;
|
||||||
|
return -1; //couldn't bind, exit
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
if(cfg.getDropPrivileges())
|
||||||
|
{
|
||||||
|
//drop privileges once we have opened the listening port
|
||||||
|
setgroups(0,NULL);
|
||||||
|
setgid(cfg.getGid());
|
||||||
|
setuid(cfg.getUid());
|
||||||
|
setuid(cfg.getUid());
|
||||||
|
}
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
/* start our cleaner thread */
|
||||||
|
if(cfg.getCleanDb())
|
||||||
|
pthread_create(&cleaner_thread,NULL,cleaner_thread_run,NULL);
|
||||||
|
|
||||||
|
new_conn_info info;
|
||||||
|
stack<new_conn_info> info_stack;
|
||||||
|
while(!quit)
|
||||||
|
{
|
||||||
|
if(server.canRead(1)) //wait one second for incoming connections, if none then loop again(allows us to check for SIGTERM and SIGINT)
|
||||||
|
{
|
||||||
|
pthread_t thread;
|
||||||
|
pthread_attr_t thread_attr;
|
||||||
|
pthread_attr_init(&thread_attr);
|
||||||
|
pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
|
||||||
|
|
||||||
|
int retval;
|
||||||
|
int fd=server.accept(&peer_address);
|
||||||
|
info.new_fd=fd;
|
||||||
|
info.peer_address=peer_address;
|
||||||
|
pthread_mutex_lock(&info_stack_mutex);
|
||||||
|
info_stack.push(info);
|
||||||
|
pthread_mutex_unlock(&info_stack_mutex);
|
||||||
|
retval=pthread_create(&thread,&thread_attr,thread_main,(void *)&info_stack);
|
||||||
|
if(retval)
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << _("Error creating thread: ") << Utils::errnotostrerror(retval) << _(". Sleeping 5 seconds before continuing...") << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
sleep(5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "[ " << ++nconns << " ] " << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
pthread_mutex_lock(&childrenlist_mutex);
|
||||||
|
children.push_back(thread);
|
||||||
|
pthread_mutex_unlock(&childrenlist_mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//close connection so that the port is no longer usable
|
||||||
|
server.close();
|
||||||
|
|
||||||
|
// wait for all threads to finish
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "Waiting for all threads to finish" << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
while(children.size())
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "Threads active:" << children.size() << (char)13;
|
||||||
|
fflush(stdout);
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
if(cfg.getCleanDb())
|
||||||
|
pthread_join(cleaner_thread,NULL);
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
|
||||||
|
#ifdef HAVE_SPF
|
||||||
|
Spf::deinitialize();
|
||||||
|
#endif //HAVE_SPF
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this threads cleans the database once each hour, deleting
|
||||||
|
* the records on the database that have an expire time < now
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void *cleaner_thread_run(void *)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Database db;
|
||||||
|
time_t next_run=time(NULL)+3600;
|
||||||
|
|
||||||
|
db.setDatabaseFile(cfg.getDatabaseFile());
|
||||||
|
|
||||||
|
db.open();
|
||||||
|
while(!quit)
|
||||||
|
{
|
||||||
|
time_t now=time(NULL);
|
||||||
|
sched_yield();
|
||||||
|
if(now>next_run)
|
||||||
|
{
|
||||||
|
unsigned long spamcount;
|
||||||
|
|
||||||
|
spamcount=db.cleanDB();
|
||||||
|
hermes_log.addMessage(LOG_DEBUG,"Cleaning database, cleaning "+Utils::inttostr(spamcount)+" blocked spams.");
|
||||||
|
if(spamcount>0&&cfg.getSubmitStats())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Socket s;
|
||||||
|
string server_response;
|
||||||
|
|
||||||
|
s.init();
|
||||||
|
s.connect("hermes-stats.iteisa.com",11125);
|
||||||
|
#ifdef HAVE_SSL
|
||||||
|
if(cfg.getSubmitStatsSsl())
|
||||||
|
{
|
||||||
|
s.writeLine("ssl");
|
||||||
|
s.enableSSL(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif //HAVE_SSL
|
||||||
|
s.writeLine("non-ssl");
|
||||||
|
s.writeLine(cfg.getSubmitStatsUsername());
|
||||||
|
s.writeLine(cfg.getSubmitStatsPassword());
|
||||||
|
s.writeLine(Utils::inttostr(spamcount));
|
||||||
|
server_response=s.readLine();
|
||||||
|
s.close();
|
||||||
|
if("OK"!=server_response)
|
||||||
|
throw Exception(server_response,__FILE__,__LINE__);
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
hermes_log.addMessage(LOG_DEBUG,"Exception sending stats: "+string(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next_run+=3600;
|
||||||
|
}
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
if(!(now%10)) //echo info each 10 seconds
|
||||||
|
{
|
||||||
|
stringstream ss;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&childrenlist_mutex);
|
||||||
|
ss << children.size() << " threads running: ";
|
||||||
|
for(list<pthread_t>::iterator i=children.begin();i!=children.end();i++)
|
||||||
|
ss << "[ " << *i << " ] ";
|
||||||
|
pthread_mutex_unlock(&childrenlist_mutex);
|
||||||
|
ss << endl;
|
||||||
|
cout << ss.str();
|
||||||
|
}
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << e << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_thread_from_list(pthread_t thread)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&childrenlist_mutex);
|
||||||
|
children.remove(thread);
|
||||||
|
pthread_mutex_unlock(&childrenlist_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *thread_main(void *info_stack)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Socket client; //for the input connection from the client
|
||||||
|
Proxy p;
|
||||||
|
new_conn_info peerinfo;
|
||||||
|
|
||||||
|
//read a new peerinfo from the stack
|
||||||
|
pthread_mutex_lock(&info_stack_mutex);
|
||||||
|
peerinfo=((stack<new_conn_info>*)info_stack)->top();
|
||||||
|
((stack<new_conn_info>*)info_stack)->pop();
|
||||||
|
pthread_mutex_unlock(&info_stack_mutex);
|
||||||
|
|
||||||
|
client.setFD(peerinfo.new_fd);
|
||||||
|
p.setOutside(client);
|
||||||
|
p.run(peerinfo.peer_address);
|
||||||
|
remove_thread_from_list(pthread_self());
|
||||||
|
}
|
||||||
|
catch(Exception &e)
|
||||||
|
{
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << e << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
hermes_log.addMessage(LOG_DEBUG,e);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void exit_requested(int)
|
||||||
|
{
|
||||||
|
if(!quit)
|
||||||
|
{
|
||||||
|
quit=true;
|
||||||
|
#ifdef REALLY_VERBOSE_DEBUG
|
||||||
|
cout << "Hit control+c again to force-quit" << endl;
|
||||||
|
#endif //REALLY_VERBOSE_DEBUG
|
||||||
|
}
|
||||||
|
else
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_pid(string file,pid_t pid)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f=fopen(file.c_str(),"w");
|
||||||
|
if(NULL==f)
|
||||||
|
throw Exception(_("Couldn't open file ")+file+_(" to write the pidfile"),__FILE__,__LINE__);
|
||||||
|
|
||||||
|
fprintf(f,"%d\n",pid);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
//pthreads on win32 doesn't provide an operator== for pthread_t
|
||||||
|
//and it's also an struct, not an int, so supply one operator== here
|
||||||
|
bool operator==(pthread_t t1,pthread_t t2)
|
||||||
|
{
|
||||||
|
return t1.p==t2.p&&t1.x==t2.x;
|
||||||
|
}
|
||||||
|
#endif //WIN32
|
36
src/hermes.h
Normal file
36
src/hermes.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#ifndef SMTPPROXY_H
|
||||||
|
#define SMTPPROXY_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "Exception.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#define _(x) (x)
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int new_fd;
|
||||||
|
std::string peer_address;
|
||||||
|
}new_conn_info;
|
||||||
|
|
||||||
|
#endif //SMTPPROXY_H
|
264
src/win32-service.cpp
Normal file
264
src/win32-service.cpp
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
/**
|
||||||
|
* hermes antispam proxy
|
||||||
|
* Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*
|
||||||
|
* 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; version 2 of the License
|
||||||
|
*
|
||||||
|
* 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.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* @author Juan José Gutiérrez de Quevedo <juanjo@gutierrezdequevedo.com>
|
||||||
|
*/
|
||||||
|
#include <string>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
#define SERVICE_NAME "hermes anti-spam proxy"
|
||||||
|
#define SERVICE_SHORT_NAME "hermes"
|
||||||
|
#define SERVICE_DESCRIPTION_TEXT "An anti-spam proxy using a combination of techniques like greylisting, dnsbl/dnswl, SPF, etc."
|
||||||
|
|
||||||
|
//macros
|
||||||
|
#define ChangeServiceStatus(x,y,z) y.dwCurrentState=z; SetServiceStatus(x,&y);
|
||||||
|
#define MIN(x,y) (((x)<(y))?(x):(y))
|
||||||
|
#define cmp(x,y) strncmp(x,y,strlen(y))
|
||||||
|
#define msgbox(x,y,z) MessageBox(NULL,x,z SERVICE_NAME,MB_OK|y)
|
||||||
|
#define winerror(x) msgbox(x,MB_ICONERROR,"Error from ")
|
||||||
|
#define winmessage(x) msgbox(x,MB_ICONINFORMATION,"Message from ")
|
||||||
|
#define condfree(x) if(NULL!=x) free(x);
|
||||||
|
#define safemalloc(x,y,z) do { if(NULL==(x=(z)malloc(y))) { winerror("Error reserving memory"); exit(-1); } memset(x,0,y); } while(0)
|
||||||
|
#define _(x) x
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The docs on microsoft's web don't seem very clear, so I have
|
||||||
|
* looked at the stunnel source code to understand how this thing
|
||||||
|
* works. What you see here is still original source, but is
|
||||||
|
* "inspired" by stunnel's source code (gui.c mainly).
|
||||||
|
* It's the real minimum needed to install, start and stop services
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern bool quit;
|
||||||
|
|
||||||
|
extern int hermes_main(int,char**);
|
||||||
|
|
||||||
|
SERVICE_STATUS service_status;
|
||||||
|
SERVICE_STATUS_HANDLE service_status_handle;
|
||||||
|
|
||||||
|
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int);
|
||||||
|
static void WINAPI service_main(DWORD,LPTSTR*);
|
||||||
|
static void WINAPI handler(DWORD);
|
||||||
|
static int service_install();
|
||||||
|
static int service_uninstall();
|
||||||
|
|
||||||
|
char **params=NULL;
|
||||||
|
#define free_params() \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
if(NULL!=params) \
|
||||||
|
{ \
|
||||||
|
condfree(params[0]); \
|
||||||
|
condfree(params[1]); \
|
||||||
|
} \
|
||||||
|
condfree(params); \
|
||||||
|
} \
|
||||||
|
while(0)
|
||||||
|
|
||||||
|
#define init_params() \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
free_params(); \
|
||||||
|
safemalloc(params,sizeof(char *)*2,char **); \
|
||||||
|
safemalloc(params[0],1*sizeof(char),char *); \
|
||||||
|
params[0][0]='\0'; \
|
||||||
|
safemalloc(params[1],1024*sizeof(char),char *); \
|
||||||
|
} \
|
||||||
|
while(0)
|
||||||
|
|
||||||
|
int WINAPI WinMain(HINSTANCE instance,HINSTANCE previous_instance,LPSTR cmdline,int cmdshow)
|
||||||
|
{
|
||||||
|
if(!cmp(cmdline,"-service"))
|
||||||
|
{
|
||||||
|
SERVICE_TABLE_ENTRY service_table[]={
|
||||||
|
{SERVICE_SHORT_NAME,service_main},
|
||||||
|
{NULL,NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(0==StartServiceCtrlDispatcher(service_table))
|
||||||
|
{
|
||||||
|
winerror("Error starting service dispatcher.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(!cmp(cmdline,"-install"))
|
||||||
|
service_install();
|
||||||
|
else if(!cmp(cmdline,"-uninstall"))
|
||||||
|
service_uninstall();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//we know that hermes can only have one parameter, so
|
||||||
|
//just copy it
|
||||||
|
init_params();
|
||||||
|
strncpy(params[1],cmdline,1024);
|
||||||
|
hermes_main(2,(char **)params);
|
||||||
|
free_params();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int service_install()
|
||||||
|
{
|
||||||
|
SC_HANDLE scm,service_handle;
|
||||||
|
SERVICE_DESCRIPTION service_description;
|
||||||
|
char filename[1024];
|
||||||
|
string exepath;
|
||||||
|
|
||||||
|
if(NULL==(scm=OpenSCManager(NULL,NULL,SC_MANAGER_CREATE_SERVICE)))
|
||||||
|
{
|
||||||
|
winerror(_("Error opening connection to the Service Manager."));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
if(0==GetModuleFileName(NULL,filename,sizeof(filename)))
|
||||||
|
{
|
||||||
|
winerror(_("Error getting the file name of the process."));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
exepath=string("\"")+filename+"\" -service";
|
||||||
|
|
||||||
|
service_handle=CreateService(
|
||||||
|
scm, //scm handle
|
||||||
|
SERVICE_SHORT_NAME, //service name
|
||||||
|
SERVICE_NAME, //display name
|
||||||
|
SERVICE_ALL_ACCESS, //desired access
|
||||||
|
SERVICE_WIN32_OWN_PROCESS, //service type
|
||||||
|
SERVICE_AUTO_START, //start type
|
||||||
|
SERVICE_ERROR_NORMAL, //error control
|
||||||
|
exepath.c_str(), //executable path with arguments
|
||||||
|
NULL, //load group
|
||||||
|
NULL, //tag for group id
|
||||||
|
NULL, //dependencies
|
||||||
|
NULL, //user name
|
||||||
|
NULL); //password
|
||||||
|
|
||||||
|
if(NULL==service_handle)
|
||||||
|
{
|
||||||
|
winerror("Error creating service. Already installed?");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
winmessage("Service successfully installed.");
|
||||||
|
|
||||||
|
//createservice doesn't have a field for description
|
||||||
|
//so we use ChangeServiceConfig2
|
||||||
|
service_description.lpDescription=SERVICE_DESCRIPTION_TEXT;
|
||||||
|
ChangeServiceConfig2(service_handle,SERVICE_CONFIG_DESCRIPTION,(void *)&service_description);
|
||||||
|
|
||||||
|
CloseServiceHandle(service_handle);
|
||||||
|
CloseServiceHandle(scm);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int service_uninstall()
|
||||||
|
{
|
||||||
|
SC_HANDLE scm,service_handle;
|
||||||
|
SERVICE_STATUS status;
|
||||||
|
|
||||||
|
if(NULL==(scm=OpenSCManager(NULL,NULL,SC_MANAGER_CREATE_SERVICE)))
|
||||||
|
{
|
||||||
|
winerror(_("Error opening connection to the Service Manager."));
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(NULL==(service_handle=OpenService(scm,SERVICE_SHORT_NAME,SERVICE_QUERY_STATUS|DELETE)))
|
||||||
|
{
|
||||||
|
winerror(_("Error opening service."));
|
||||||
|
CloseServiceHandle(scm);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(0==QueryServiceStatus(service_handle,&status))
|
||||||
|
{
|
||||||
|
winerror(_("Error querying service."));
|
||||||
|
CloseServiceHandle(scm);
|
||||||
|
CloseServiceHandle(service_handle);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SERVICE_STOPPED!=status.dwCurrentState)
|
||||||
|
{
|
||||||
|
winerror(SERVICE_NAME _(" is still running. Stop it before trying to uninstall it."));
|
||||||
|
CloseServiceHandle(scm);
|
||||||
|
CloseServiceHandle(service_handle);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(0==DeleteService(service_handle))
|
||||||
|
{
|
||||||
|
winerror(_("Error deleting service."));
|
||||||
|
CloseServiceHandle(scm);
|
||||||
|
CloseServiceHandle(service_handle);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseServiceHandle(scm);
|
||||||
|
CloseServiceHandle(service_handle);
|
||||||
|
winmessage(_("Service successfully uninstalled."));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WINAPI service_main(DWORD argc,LPTSTR *argv)
|
||||||
|
{
|
||||||
|
char *tmpstr;
|
||||||
|
|
||||||
|
//configure service_status structure
|
||||||
|
service_status.dwServiceType=SERVICE_WIN32;
|
||||||
|
service_status.dwControlsAccepted=0;
|
||||||
|
service_status.dwWin32ExitCode=NO_ERROR;
|
||||||
|
service_status.dwServiceSpecificExitCode=NO_ERROR;
|
||||||
|
service_status.dwCheckPoint=0;
|
||||||
|
service_status.dwWaitHint=0;
|
||||||
|
service_status.dwControlsAccepted|=SERVICE_ACCEPT_STOP;
|
||||||
|
|
||||||
|
service_status_handle=RegisterServiceCtrlHandler(SERVICE_SHORT_NAME,handler);
|
||||||
|
|
||||||
|
if(0!=service_status_handle)
|
||||||
|
{
|
||||||
|
//set service status
|
||||||
|
ChangeServiceStatus(service_status_handle,service_status,SERVICE_RUNNING);
|
||||||
|
|
||||||
|
//get the path to the config file
|
||||||
|
init_params();
|
||||||
|
GetModuleFileName(NULL,params[1],1024);
|
||||||
|
if(NULL==(tmpstr=strrchr(params[1],'\\'))) { winerror("Error finding default config file."); exit(-1); }
|
||||||
|
*(++tmpstr)='\0';
|
||||||
|
strncat(params[1],"hermes.ini",strlen("hermes.ini"));
|
||||||
|
|
||||||
|
//now start our main program
|
||||||
|
hermes_main(2,(char **)params);
|
||||||
|
|
||||||
|
free_params();
|
||||||
|
|
||||||
|
//when we are here, we have been stopped
|
||||||
|
ChangeServiceStatus(service_status_handle,service_status,SERVICE_STOP_PENDING);
|
||||||
|
ChangeServiceStatus(service_status_handle,service_status,SERVICE_STOPPED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WINAPI handler(DWORD code)
|
||||||
|
{
|
||||||
|
if(SERVICE_CONTROL_STOP==code)
|
||||||
|
{
|
||||||
|
quit=true;
|
||||||
|
ChangeServiceStatus(service_status_handle,service_status,SERVICE_STOP_PENDING);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue