commit 2aac3e1e88bbaf0a6fc3d882043532b947e65fc2 Author: ps Date: Sun Dec 14 19:12:48 2008 +0000 move all code into /trunk diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..fbfdd43 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,7 @@ +Juan José Gutiérrez de Quevedo + Main coder + +Veit Wahlich + .spec file rewrite + patch for rejection if peer's ip doesn't resolve to a hostname + patch for stats transmission exactly every 60 minutes diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..d8e031c --- /dev/null +++ b/ChangeLog @@ -0,0 +1,335 @@ +ChangeLog +--------- + +2007-07-20 20:03 Juan José Gutiérrez de Quevedo + +* 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 + +* 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 + +* 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 + +* 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 + +* 1.2 release + +* added rbl checking. Simply define rbl_domain in configfile + +2007-04-20 12:04 Juan José Gutiérrez de Quevedo + +* Added an option to configure the initial delay of the SMTP banner + +2007-04-19 20:28 Juan José Gutiérrez de Quevedo + +* 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 + +* Initial 1.0 release + +2007-04-09 20:27 Juan José Gutiérrez de Quevedo + +* *.{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 + +* *.{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 + +* *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 + +* *: change all instances of spit to hermes to reflect project's new name + +2007-03-09 18:19 Juan José Gutiérrez de Quevedo + +* 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 + +* *.*: 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 + +* 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 + +* 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 + +* 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 + +* 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 + +* 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 + +* 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 + +* 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 + +* 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 + +* 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 + +* main.cpp (main): Made threads detached to allow them to free resources + +2006-09-17 Juan José Gutiérrez de Quevedo + +* 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 +* Initial import to svn diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..39a3fd8 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,3 @@ +EXTRA_DIST = scripts/generate_config.pl ChangeLog TODO + +SUBDIRS = src docs dists diff --git a/TODO b/TODO new file mode 100644 index 0000000..5c40316 --- /dev/null +++ b/TODO @@ -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 diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..1aabc7e --- /dev/null +++ b/bootstrap @@ -0,0 +1,3 @@ +#!/bin/bash + +aclocal && autoconf && autoheader && automake --add-missing diff --git a/configure.in b/configure.in new file mode 100644 index 0000000..ae8a0d0 --- /dev/null +++ b/configure.in @@ -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 diff --git a/dists/Makefile.am b/dists/Makefile.am new file mode 100644 index 0000000..092a8ea --- /dev/null +++ b/dists/Makefile.am @@ -0,0 +1,2 @@ +doc_DATA = hermesrc.example +EXTRA_DIST = fc_init hermes.spec hermes.spec.in diff --git a/dists/fc_init b/dists/fc_init new file mode 100755 index 0000000..f4f30a4 --- /dev/null +++ b/dists/fc_init @@ -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 diff --git a/dists/hermes.spec.in b/dists/hermes.spec.in new file mode 100644 index 0000000..c69296c --- /dev/null +++ b/dists/hermes.spec.in @@ -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 +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 1.4 +- removed patches, they are now on upstream + +* Fri May 25 2007 Veit Wahlich 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 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 +- Fixed rpm to create /var/hermes + +* Fri Apr 11 2007 Juan José Gutiérrez de Quevedo +- Initial release diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 0000000..0356632 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,1252 @@ +# Doxyfile 1.4.7 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = spit + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ./ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, +# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, +# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, +# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, +# Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = Spanish + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = YES + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to +# include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from the +# version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../src + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a caller dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that a graph may be further truncated if the graph's +# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH +# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), +# the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/docs/Makefile.am b/docs/Makefile.am new file mode 100644 index 0000000..798a527 --- /dev/null +++ b/docs/Makefile.am @@ -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 diff --git a/docs/gpl.txt b/docs/gpl.txt new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/docs/gpl.txt @@ -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. + + + Copyright (C) + + 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. + + , 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. diff --git a/docs/hermes-options.html.in b/docs/hermes-options.html.in new file mode 100644 index 0000000..59abb07 --- /dev/null +++ b/docs/hermes-options.html.in @@ -0,0 +1,8 @@ +
+

%name%

+
Type: %type%
+
Default value: %default%
+
+ %explanation% +
+
diff --git a/docs/installing-hermes.txt b/docs/installing-hermes.txt new file mode 100644 index 0000000..9ae937a --- /dev/null +++ b/docs/installing-hermes.txt @@ -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 +" 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. diff --git a/scripts/generate_config.pl b/scripts/generate_config.pl new file mode 100755 index 0000000..d63156d --- /dev/null +++ b/scripts/generate_config.pl @@ -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("",); +close HTMLIN; + +while(<>) +{ + chomp; + if(! /^#/ && ! /^\t*$/ && ! /^\*/) + { + s/^\s+//;s/\s+$//; + @_=split ","; + my $camelcased=&camel_case($_[1]); + my $type=$_[0]; + $type="list" 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, "); +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, "); +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 + * + * 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 + */ +#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 Configfile::parseAsList(string str) +{ + list tmpList; + string::size_type startpos=0,endpos=0,len; + string tmpstr; + + str=Configfile::parseAsString(str); //remove quotes around string + + len=str.length(); + while(startpos + * + * 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 + */ +#ifndef CONFIGFILE_H +#define CONFIGFILE_H + +#include "hermes.h" +#include +#include +#include +#include + +#include "Utils.h" + +using namespace std; + +class Configfile +{ + private: + static string parseAsString(string); + static bool parseAsBool(string); + static long parseAsInt(string); + static list parseAsList(string); + int uid; + int gid; + %templ_privateattribs% + public: + Configfile(); + void parse(string); + void validateConfig(); + int getUid(); + int getGid(); + %templ_publicmethods% +}; + +#endif //CONFIGFILE_H diff --git a/src/Configfile.tmpl b/src/Configfile.tmpl new file mode 100644 index 0000000..e21045e --- /dev/null +++ b/src/Configfile.tmpl @@ -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 diff --git a/src/Database.cpp b/src/Database.cpp new file mode 100644 index 0000000..2d3590f --- /dev/null +++ b/src/Database.cpp @@ -0,0 +1,427 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#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;i31&&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(now0) + 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 + * + * 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 + */ +#ifndef DATABASE_H +#define DATABASE_H + +#include "hermes.h" + +#include +#include +#include +#include + +#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 diff --git a/src/Exception.cpp b/src/Exception.cpp new file mode 100644 index 0000000..46021d2 --- /dev/null +++ b/src/Exception.cpp @@ -0,0 +1,146 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#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\"","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\""); + 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\"\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 diff --git a/src/Exception.h b/src/Exception.h new file mode 100644 index 0000000..6a525a4 --- /dev/null +++ b/src/Exception.h @@ -0,0 +1,64 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef EXCEPTION_H +#define EXCEPTION_H + +#include +#include + +#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 diff --git a/src/FileLogger.cpp b/src/FileLogger.cpp new file mode 100644 index 0000000..2f06894 --- /dev/null +++ b/src/FileLogger.cpp @@ -0,0 +1,156 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#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::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)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; +} diff --git a/src/FileLogger.h b/src/FileLogger.h new file mode 100644 index 0000000..d12f190 --- /dev/null +++ b/src/FileLogger.h @@ -0,0 +1,58 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef FILELOGGER_H +#define FILELOGGER_H + +#include "config.h" +#include +#include +#include +#include + +#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 tmpstrings; + void openFile(string); + void closeFile(); + void syncBufferToDisk(); + void rotateLog(); + string getProcessedRotateFilename(); + public: + FileLogger(); + ~FileLogger(); + void addMessage(int,string); +}; + +#endif //FILELOGGER_H diff --git a/src/Logger.h b/src/Logger.h new file mode 100644 index 0000000..ef99716 --- /dev/null +++ b/src/Logger.h @@ -0,0 +1,49 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef LOGGER_H +#define LOGGER_H + +#include + +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 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..a49f270 --- /dev/null +++ b/src/Makefile.am @@ -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 diff --git a/src/NullLogger.h b/src/NullLogger.h new file mode 100644 index 0000000..a513835 --- /dev/null +++ b/src/NullLogger.h @@ -0,0 +1,41 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef NULLLOGGER_H +#define NULLLOGGER_H + +#include + +#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 diff --git a/src/Proxy.cpp b/src/Proxy.cpp new file mode 100644 index 0000000..851f43a --- /dev/null +++ b/src/Proxy.cpp @@ -0,0 +1,334 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#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 to "+(""==to?"no-to":to)); + return; + } +} diff --git a/src/Proxy.h b/src/Proxy.h new file mode 100644 index 0000000..fd55da2 --- /dev/null +++ b/src/Proxy.h @@ -0,0 +1,61 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef PROXY_H +#define PROXY_H + +#include "hermes.h" +#include +#ifdef WIN32 + #include +#else + #include +#endif +#include +#include +#include +#include +#include +#include + +#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 diff --git a/src/ServerSocket.cpp b/src/ServerSocket.cpp new file mode 100644 index 0000000..bd99594 --- /dev/null +++ b/src/ServerSocket.cpp @@ -0,0 +1,88 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#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; +} diff --git a/src/ServerSocket.h b/src/ServerSocket.h new file mode 100644 index 0000000..3349d79 --- /dev/null +++ b/src/ServerSocket.h @@ -0,0 +1,57 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef SERVERSOCKET_H +#define SERVERSOCKET_H + +#include "config.h" + +#include +#ifdef WIN32 + #include +#else + #include + #include + #include +#endif //WIN32 + +#include + +#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 diff --git a/src/Socket.cpp b/src/Socket.cpp new file mode 100644 index 0000000..d768116 --- /dev/null +++ b/src/Socket.cpp @@ -0,0 +1,577 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#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(readed0) + 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 +} diff --git a/src/Socket.h b/src/Socket.h new file mode 100644 index 0000000..96ff646 --- /dev/null +++ b/src/Socket.h @@ -0,0 +1,103 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef SOCKET_H +#define SOCKET_H + +#include "hermes.h" + +#include +#include +#include +#include +#ifdef WIN32 + #include + #include + #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 + #include + #include +#endif //WIN32 +#include +#ifdef HAVE_SSL +#include +#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 diff --git a/src/Spf.cpp b/src/Spf.cpp new file mode 100644 index 0000000..1ef0bf2 --- /dev/null +++ b/src/Spf.cpp @@ -0,0 +1,99 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#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; +} diff --git a/src/Spf.h b/src/Spf.h new file mode 100644 index 0000000..9e1a07f --- /dev/null +++ b/src/Spf.h @@ -0,0 +1,44 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef SPF_H +#define SPF_H + +#include "hermes.h" + +extern "C" +{ +#include +} + +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 diff --git a/src/UnixLogger.cpp b/src/UnixLogger.cpp new file mode 100644 index 0000000..c6c8a86 --- /dev/null +++ b/src/UnixLogger.cpp @@ -0,0 +1,35 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#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()); +} diff --git a/src/UnixLogger.h b/src/UnixLogger.h new file mode 100644 index 0000000..07ca02e --- /dev/null +++ b/src/UnixLogger.h @@ -0,0 +1,43 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef UNIXLOGGER_H +#define UNIXLOGGER_H + +#include +#include + +#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 diff --git a/src/Utils.cpp b/src/Utils.cpp new file mode 100644 index 0000000..19bf83e --- /dev/null +++ b/src/Utils.cpp @@ -0,0 +1,580 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#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 + * +------------------------------------------+ + * | |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 + * + * + * @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 + * + * @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& 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::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); +} diff --git a/src/Utils.h b/src/Utils.h new file mode 100644 index 0000000..f3ff02c --- /dev/null +++ b/src/Utils.h @@ -0,0 +1,84 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef UTILS_H +#define UTILS_H + +#include "hermes.h" +#include +#include +#include +#include +#include + +#ifndef WIN32 + #include + #include +#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&,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 diff --git a/src/hermes.cpp b/src/hermes.cpp new file mode 100644 index 0000000..ce9ff7f --- /dev/null +++ b/src/hermes.cpp @@ -0,0 +1,413 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SSL +#include +#endif //HAVE_SSL +#ifndef WIN32 +#include +#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 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;i0) + 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 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::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*)info_stack)->top(); + ((stack*)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 diff --git a/src/hermes.h b/src/hermes.h new file mode 100644 index 0000000..5578b68 --- /dev/null +++ b/src/hermes.h @@ -0,0 +1,36 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#ifndef SMTPPROXY_H +#define SMTPPROXY_H + +#include "config.h" +#include "Exception.h" +#include +#include + +#define _(x) (x) + +typedef struct +{ + int new_fd; + std::string peer_address; +}new_conn_info; + +#endif //SMTPPROXY_H diff --git a/src/win32-service.cpp b/src/win32-service.cpp new file mode 100644 index 0000000..43d71eb --- /dev/null +++ b/src/win32-service.cpp @@ -0,0 +1,264 @@ +/** + * hermes antispam proxy + * Copyright (C) 2006, 2007 Juan José Gutiérrez de Quevedo + * + * 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 + */ +#include +#include + +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); + } +}