diff --git a/ChangeLog b/ChangeLog index 9d10e94..be35ad3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,15 @@ E-MailRelay Change Log ====================== +1.1.1 -> 1.1.2 +-------------- +* Earlier check for un-bindable ports on startup, and later fork()ing [bug-id 776972]. +* Resolved the file-descriptor kludge for "--verifier" on Windows. +* Less strict about failing eight bit messages sent to servers with no "8BITMIME" extension. +* Supplementary group memberships revoked at startup if root or suid. +* Pre-processor ("--filter") program's standard output searched for a failure reason string. +* Undocumented "--scanner" switch added for asynchronous processing by a separate network server. + 1.1.0 -> 1.1.1 -------------- * Restored the fix for building with gcc2.96. diff --git a/acinclude.m4 b/acinclude.m4 index 6a47654..b271b63 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -23,18 +23,38 @@ dnl AC_DEFUN(ACLOCAL_TYPE_SOCKLEN_T, [AC_CACHE_CHECK([for socklen_t], aclocal_cv_type_socklen_t, [ - AC_TRY_COMPILE( - [#include + AC_TRY_COMPILE( + [#include #include ], - [socklen_t len = 42; return len;], - aclocal_cv_type_socklen_t=yes, - aclocal_cv_type_socklen_t=no) + [socklen_t len = 42; return len;], + aclocal_cv_type_socklen_t=yes, + aclocal_cv_type_socklen_t=no ) ]) - if test $aclocal_cv_type_socklen_t = yes; then - AC_DEFINE(HAVE_SOCKLEN_T, 1,[Define if socklen_t type definition in sys/socket.h]) - else - AC_DEFINE(HAVE_SOCKLEN_T, 0,[Define if socklen_t type definition in sys/socket.h]) - fi + if test $aclocal_cv_type_socklen_t = yes; then + AC_DEFINE(HAVE_SOCKLEN_T, 1,[Define if socklen_t type definition in sys/socket.h]) + else + AC_DEFINE(HAVE_SOCKLEN_T, 0,[Define if socklen_t type definition in sys/socket.h]) + fi +]) + +dnl setgroups +dnl +AC_DEFUN([ACLOCAL_CHECK_SETGROUPS], +[AC_CACHE_CHECK([for setgroups], aclocal_cv_setgroups, +[ + AC_TRY_COMPILE( + [#include +#include +#include ], + [setgroups(0,0) ;], + aclocal_cv_setgroups=yes , + aclocal_cv_setgroups=no ) +]) + if test $aclocal_cv_setgroups = yes; then + AC_DEFINE(HAVE_SETGROUPS,1,[Define if setgroups is available]) + else + AC_DEFINE(HAVE_SETGROUPS,0,[Define if setgroups is available]) + fi ]) dnl gmtime_r diff --git a/aclocal.m4 b/aclocal.m4 index ba3a761..6189133 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -36,18 +36,38 @@ dnl AC_DEFUN(ACLOCAL_TYPE_SOCKLEN_T, [AC_CACHE_CHECK([for socklen_t], aclocal_cv_type_socklen_t, [ - AC_TRY_COMPILE( - [#include + AC_TRY_COMPILE( + [#include #include ], - [socklen_t len = 42; return len;], - aclocal_cv_type_socklen_t=yes, - aclocal_cv_type_socklen_t=no) + [socklen_t len = 42; return len;], + aclocal_cv_type_socklen_t=yes, + aclocal_cv_type_socklen_t=no ) ]) - if test $aclocal_cv_type_socklen_t = yes; then - AC_DEFINE(HAVE_SOCKLEN_T, 1,[Define if socklen_t type definition in sys/socket.h]) - else - AC_DEFINE(HAVE_SOCKLEN_T, 0,[Define if socklen_t type definition in sys/socket.h]) - fi + if test $aclocal_cv_type_socklen_t = yes; then + AC_DEFINE(HAVE_SOCKLEN_T, 1,[Define if socklen_t type definition in sys/socket.h]) + else + AC_DEFINE(HAVE_SOCKLEN_T, 0,[Define if socklen_t type definition in sys/socket.h]) + fi +]) + +dnl setgroups +dnl +AC_DEFUN([ACLOCAL_CHECK_SETGROUPS], +[AC_CACHE_CHECK([for setgroups], aclocal_cv_setgroups, +[ + AC_TRY_COMPILE( + [#include +#include +#include ], + [setgroups(0,0) ;], + aclocal_cv_setgroups=yes , + aclocal_cv_setgroups=no ) +]) + if test $aclocal_cv_setgroups = yes; then + AC_DEFINE(HAVE_SETGROUPS,1,[Define if setgroups is available]) + else + AC_DEFINE(HAVE_SETGROUPS,0,[Define if setgroups is available]) + fi ]) dnl gmtime_r diff --git a/bin/emailrelay-test.sh_ b/bin/emailrelay-test.sh_ index bd9cc30..edcf557 100644 --- a/bin/emailrelay-test.sh_ +++ b/bin/emailrelay-test.sh_ @@ -60,7 +60,8 @@ base_dir="/tmp/`basename $0`.$$.tmp" summary_log="/tmp/`basename $0`.out" exit_code="1" watchdog_pid="" -export MALLOC_CHECK_="2" +MALLOC_CHECK_="2" +export MALLOC_CHECK_ ulimit -c 1000000 if test "${use_valgrind}" -eq 1 then diff --git a/bin/emailrelay.sh_ b/bin/emailrelay.sh_ index bf6202f..bf87fa8 100644 --- a/bin/emailrelay.sh_ +++ b/bin/emailrelay.sh_ @@ -176,6 +176,7 @@ redhat_cmd_start() redhat_errno="$?" #touch /var/lock/subsys/emailrelay if test "${redhat_errno}" -eq 0 ; then success ; else failure ; fi + echo } redhat_cmd_stop() @@ -185,6 +186,7 @@ redhat_cmd_stop() redhat_errno="$?" #rm -f /var/lock/subsys/emailrelay if test "${redhat_errno}" -eq 0 ; then success ; else failure ; fi + echo } redhat_cmd_restarted() diff --git a/bin/expand.sh_ b/bin/expand.sh_ index 64e5e31..f2f1917 100644 --- a/bin/expand.sh_ +++ b/bin/expand.sh_ @@ -29,8 +29,9 @@ # The "-t" switch can be used to suppress expansion where the included # file has an extension of ".html". # -# In edit mode (--edit) the exit value is 0 (shell true) if the file is -# modified. This allows for recursive expansion using a shell while loop. +# In edit mode (--edit) the specified file is modified in-place. With this +# switch the exit value is 0 (shell true) if the file is modified in any +# way. This allows for recursive expansion using a shell while loop. # # usage: expand.sh [-a ] [-t] { --edit | [ ...] } # diff --git a/config.h.in b/config.h.in index 4c0e47f..152d8a1 100644 --- a/config.h.in +++ b/config.h.in @@ -25,6 +25,9 @@ /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_NDIR_H +/* Define if setgroups is available */ +#undef HAVE_SETGROUPS + /* Define if socklen_t type definition in sys/socket.h */ #undef HAVE_SOCKLEN_T diff --git a/configure b/configure index 0f936c0..7b28a7f 100755 --- a/configure +++ b/configure @@ -1459,7 +1459,7 @@ fi # Define the identity of the package. PACKAGE=emailrelay - VERSION=1.1.1 + VERSION=1.1.2 cat >>confdefs.h <<_ACEOF @@ -4565,7 +4565,7 @@ if test "${aclocal_cv_type_socklen_t+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else - cat >conftest.$ac_ext <<_ACEOF + cat >conftest.$ac_ext <<_ACEOF #line $LINENO "configure" #include "confdefs.h" #include @@ -4607,19 +4607,19 @@ rm -f conftest.$ac_objext conftest.$ac_ext fi echo "$as_me:$LINENO: result: $aclocal_cv_type_socklen_t" >&5 echo "${ECHO_T}$aclocal_cv_type_socklen_t" >&6 - if test $aclocal_cv_type_socklen_t = yes; then + if test $aclocal_cv_type_socklen_t = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_SOCKLEN_T 1 _ACEOF - else + else cat >>confdefs.h <<\_ACEOF #define HAVE_SOCKLEN_T 0 _ACEOF - fi + fi echo "$as_me:$LINENO: checking for buggy ctime" >&5 echo $ECHO_N "checking for buggy ctime... $ECHO_C" >&6 @@ -4805,6 +4805,69 @@ _ACEOF fi +echo "$as_me:$LINENO: checking for setgroups" >&5 +echo $ECHO_N "checking for setgroups... $ECHO_C" >&6 +if test "${aclocal_cv_setgroups+set}" = set; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + + cat >conftest.$ac_ext <<_ACEOF +#line $LINENO "configure" +#include "confdefs.h" +#include +#include +#include +#ifdef F77_DUMMY_MAIN +# ifdef __cplusplus + extern "C" +# endif + int F77_DUMMY_MAIN() { return 1; } +#endif +int +main () +{ +setgroups(0,0) ; + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext +if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && + { ac_try='test -s conftest.$ac_objext' + { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5 + (eval $ac_try) 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); }; }; then + aclocal_cv_setgroups=yes +else + echo "$as_me: failed program was:" >&5 +cat conftest.$ac_ext >&5 +aclocal_cv_setgroups=no +fi +rm -f conftest.$ac_objext conftest.$ac_ext + +fi +echo "$as_me:$LINENO: result: $aclocal_cv_setgroups" >&5 +echo "${ECHO_T}$aclocal_cv_setgroups" >&6 + if test $aclocal_cv_setgroups = yes; then + +cat >>confdefs.h <<\_ACEOF +#define HAVE_SETGROUPS 1 +_ACEOF + + else + +cat >>confdefs.h <<\_ACEOF +#define HAVE_SETGROUPS 0 +_ACEOF + + fi + # Check whether --enable-debug or --disable-debug was given. if test "${enable_debug+set}" = set; then diff --git a/configure.ac b/configure.ac old mode 100755 new mode 100644 index 0637957..c13c4aa --- a/configure.ac +++ b/configure.ac @@ -21,7 +21,7 @@ dnl Process this file with autoconf to produce a configure script. dnl AC_INIT(src/gsmtp/gsmtp.h) -AM_INIT_AUTOMAKE(emailrelay,1.1.1) +AM_INIT_AUTOMAKE(emailrelay,1.1.2) AM_CONFIG_HEADER(config.h) dnl === @@ -60,6 +60,7 @@ ACLOCAL_TYPE_SOCKLEN_T ACLOCAL_CHECK_BUGGY_CTIME ACLOCAL_CHECK_GMTIME_R ACLOCAL_CHECK_LOCALTIME_R +ACLOCAL_CHECK_SETGROUPS dnl === dnl "--enable-debug" diff --git a/doc/Makefile.am b/doc/Makefile.am index d9694d8..0a40f26 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -25,12 +25,12 @@ man_files_out=emailrelay.1.gz emailrelay-passwd.1.gz emailrelay-poke.1.gz emailr html_files_in=doxygen_header.html html_files_thru=index.html emailrelay-man.html $(stylesheet) html_files_out=readme.html developer.html reference.html userguide.html windows.html changelog.html -png_files=gsmtp-classes.png gnet-classes.png +png_files=gsmtp-classes.png gnet-classes.png sequence-1.png sequence-2.png sequence-3.png sequence-4.png gnet-client.png gsmtp-serverprotocol.png gsmtp-scannerclient.png EXTRA_DIST = $(man_files_in) $(txt_files) $(html_files_in) $(html_files_thru) $(png_files) noinst_SCRIPTS = .dox e_man1_DATA = $(man_files_out) -e_doc_DATA = $(txt_files) $(html_files_out) $(html_files_thru) +e_doc_DATA = $(txt_files) $(html_files_out) $(html_files_thru) $(png_files) CLEANFILES = $(noinst_SCRIPTS) $(man_files_out) $(html_files_out) doxygen/* SUFFIXES = .txt .html diff --git a/doc/Makefile.in b/doc/Makefile.in index 1caf7e0..636d33f 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -100,12 +100,12 @@ man_files_out = emailrelay.1.gz emailrelay-passwd.1.gz emailrelay-poke.1.gz emai html_files_in = doxygen_header.html html_files_thru = index.html emailrelay-man.html $(stylesheet) html_files_out = readme.html developer.html reference.html userguide.html windows.html changelog.html -png_files = gsmtp-classes.png gnet-classes.png +png_files = gsmtp-classes.png gnet-classes.png sequence-1.png sequence-2.png sequence-3.png sequence-4.png gnet-client.png gsmtp-serverprotocol.png gsmtp-scannerclient.png EXTRA_DIST = $(man_files_in) $(txt_files) $(html_files_in) $(html_files_thru) $(png_files) noinst_SCRIPTS = .dox e_man1_DATA = $(man_files_out) -e_doc_DATA = $(txt_files) $(html_files_out) $(html_files_thru) +e_doc_DATA = $(txt_files) $(html_files_out) $(html_files_thru) $(png_files) CLEANFILES = $(noinst_SCRIPTS) $(man_files_out) $(html_files_out) doxygen/* SUFFIXES = .txt .html diff --git a/doc/developer.txt b/doc/developer.txt index dd6d513..dc0d0e4 100644 --- a/doc/developer.txt +++ b/doc/developer.txt @@ -1,5 +1,5 @@ -E-MailRelay Developer guide -=========================== +E-MailRelay Design and implementation +===================================== Module structure ---------------- @@ -27,8 +27,8 @@ reading and extracting messages from the store. The concrete implementation classes based on these interfaces are respectively "FileStore", "NewFile" and "StoredFile". -The interaction betweeen the server protocol class and the message store is -mediated by the "ProtocolMessage" interface. Two implementations of this +The interaction between the server protocol class and the message store is +mediated by the "ProtocolMessage" interface. Two main implementations of this interface are supplied: one for normal spooling ("ProtocolMessageStore"), and another for immediate forwarding ("ProtocolMessageForward"). @@ -78,19 +78,32 @@ Directory structure Standard headers which are missing (or broken) in msvc6.0 -Tour ----- -For a quick bottom-up tour of the code take a look at the following headers: -* src/glib/gpath.h -* src/glib/gstr.h -* lib/gcc2.95/sstream -* src/gnet/gevent.h -* src/gnet/gaddress.h -* src/gnet/gsocket.h -* src/gnet/gserver.h -* src/smtp/gmessagestore.h -* src/smtp/gserverprotocol.h -* src/smtp/gsmtpserver.h +Event model +----------- +The E-MailRelay server uses non-blocking socket i/o, with a select() event loop. +This event model means that the server can handle multiple clients simultaneously +from a single thread. The only significant blocking occurs when external programs +are executed (see "--filter" and "--verifier"). + +At higher levels the slot/signal design pattern is used to propagate events +between objects. + +Diagrams +-------- +Class diagrams: +* *GNet namespace* [gnet-classes.png] +* *GSmtp namespace* [gsmtp-classes.png] + +State transition diagrams: +* *GNet::Client* [gnet-client.png] +* *GSmtp::ServerProtocol* [gsmtp-serverprotocol.png] +* *GSmtp::ScannerClient* [gsmtp-scannerclient.png] + +Sequence diagrams: +* *Proxy mode forwarding* [sequence-3.png] +* *Scanning* [sequence-4.png] +* *ProtocolMessage::prepare() returns false* [sequence-1.png] +* *ProtocolMessage::prepare() returns true* [sequence-2.png] Portability ----------- @@ -139,7 +152,7 @@ these cases there are three source files per header. For example, "gsocket.cpp", Compile-time features --------------------- -The following features are available to source-code hackers: +The following compile-time features can be enabled with some effort: # IPv6 @@ -162,8 +175,11 @@ The following features are available to source-code hackers: Windows build ------------- -A simple project file "emailrelay.dsp" for msvc6.0 is provided in the "src/main" -directory. +Simple MSVC project files are provided in the "src/main" directory, bundled into +the "emailrelay.dws" workspace. + +Makefiles for the *MinGW* [http://www.mingw.org/] system (gcc on Windows) are also +provided. Style ----- diff --git a/doc/emailrelay-man.html b/doc/emailrelay-man.html index 18255f6..07c36f3 100644 --- a/doc/emailrelay-man.html +++ b/doc/emailrelay-man.html @@ -428,7 +428,7 @@ Graeme Walker, mailto:graem This document was created by man2html, using the manual pages.
-Time: 12:51:06 GMT, July 05, 2003 +Time: 14:14:01 GMT, September 07, 2003 diff --git a/doc/emailrelay.css b/doc/emailrelay.css index be33ca6..7e003ac 100644 --- a/doc/emailrelay.css +++ b/doc/emailrelay.css @@ -102,6 +102,20 @@ a.a-toc:hover background-color: #ccc ; } +a.a-toc-expander +{ + color: #09c ; + background-color: inherit ; + text-decoration: none ; + font-size: smaller ; +} + +a.a-toc-expander:hover +{ + color: #09c ; + background-color: #ccc ; +} + a.a-header { text-decoration:none ; diff --git a/doc/gnet-client.png b/doc/gnet-client.png new file mode 100644 index 0000000..2b87a37 Binary files /dev/null and b/doc/gnet-client.png differ diff --git a/doc/gsmtp-scannerclient.png b/doc/gsmtp-scannerclient.png new file mode 100644 index 0000000..aa5aedc Binary files /dev/null and b/doc/gsmtp-scannerclient.png differ diff --git a/doc/gsmtp-serverprotocol.png b/doc/gsmtp-serverprotocol.png new file mode 100644 index 0000000..34857e5 Binary files /dev/null and b/doc/gsmtp-serverprotocol.png differ diff --git a/doc/index.html b/doc/index.html index a0f05c9..297b3b2 100644 --- a/doc/index.html +++ b/doc/index.html @@ -13,7 +13,7 @@
  • User guide
  • Reference
  • Windows installation guide
  • -
  • Notes for developers
  • +
  • Design and implementation
  • Source code documentation (generated by doxygen, if available)
  • Man page (generated by man2html, if available)
  • Web site
  • diff --git a/doc/reference.txt b/doc/reference.txt index b358066..c3fd397 100644 --- a/doc/reference.txt +++ b/doc/reference.txt @@ -251,11 +251,16 @@ success, or a value between 1 and 99 to indicate failure. Exit codes between further processing of the message, and 103 has the effect of immediately expiring any "--poll" timer. +If the pre-processor program terminates with a non-zero exit code then the first +few thousand characters of the standard output stream are searched for a line +starting with "<<" followed by ">>". The text inbetween is taken as a failure +reason, and passed back to the SMTP client. + The pre-processor program can edit any part of the message's envelope file or content file: E-MailRelay remembers nothing about the message while the pre-processor is running, except the filename. But if the message is deleted -by the pre-processor then E-MailRelay will be upset, unless the exit code -is 100. +by the pre-processor then E-MailRelay will be upset, so to avoid the +error message use an exit code of 100. If the pre-processor program creates new messages in the spool directory then they may not be processed immediately, or they may be completely ignored. To get @@ -381,8 +386,8 @@ following precautions are taken: # execution environment The mail pre-processor runs with an almost empty set of environment variables - ("PATH" and "IFS"), and with no open file descriptors other than "stdin"/"stdout"/"stderr" - open onto "/dev/null". + ("PATH" and "IFS"), and with no open file descriptors other than "stdin" and + "stderr" open onto "/dev/null", and "stdout" open onto a pipe. # configuration diff --git a/doc/sequence-1.png b/doc/sequence-1.png new file mode 100644 index 0000000..89f259f Binary files /dev/null and b/doc/sequence-1.png differ diff --git a/doc/sequence-2.png b/doc/sequence-2.png new file mode 100644 index 0000000..04669a5 Binary files /dev/null and b/doc/sequence-2.png differ diff --git a/doc/sequence-3.png b/doc/sequence-3.png new file mode 100644 index 0000000..283193c Binary files /dev/null and b/doc/sequence-3.png differ diff --git a/doc/sequence-4.png b/doc/sequence-4.png new file mode 100644 index 0000000..02e8647 Binary files /dev/null and b/doc/sequence-4.png differ diff --git a/doc/userguide.txt b/doc/userguide.txt index 3442ae9..38d5f6c 100644 --- a/doc/userguide.txt +++ b/doc/userguide.txt @@ -12,7 +12,7 @@ the outside world, using the SMTP protocol. As soon as an e-mail message is received it is forwarded on to the next SMTP server for onward delivery. This becomes more useful when you add in your own message processing: as each message is received it can be passed one of your programs for editing, -filtering, encypting etc. +filtering, encrypting etc. When used as a store-and-forward transfer agent E-MailRelay runs in two modes: the storage daemon part, and the forwarding agent. The storage daemon diff --git a/doc/windows.txt b/doc/windows.txt index 731d24e..29b58ef 100644 --- a/doc/windows.txt +++ b/doc/windows.txt @@ -1,5 +1,5 @@ -E-MailRelay for Windows -======================= +E-MailRelay Windows installation +================================ Introduction ------------ @@ -92,14 +92,6 @@ Finally you will need to configure your e-mail client program to use the local E-MailRelay server for outgoing mail. Where it asks for the name of the SMTP server for outgoing mail you should tell it to use "localhost" or "127.0.0.1". -COM registration ----------------- -The server can be optionally registered as a COM component by running the -executable with the "-regserver" command line switch. The COM interface may -allow stored messages to be forwarded to any remote server, so consider the -security implications before doing the COM registration. Refer to the reference -guide for more details. - Uninstall --------- If you have ever registered the executable as a COM component, then deregister it diff --git a/emailrelay.spec b/emailrelay.spec index 986a3ce..c20de07 100644 --- a/emailrelay.spec +++ b/emailrelay.spec @@ -1,10 +1,10 @@ Summary: Simple e-mail message transfer agent using SMTP Name: emailrelay -Version: 1.1.1 +Version: 1.1.2 Release: 1 Copyright: GPL Group: System Environment/Daemons -Source: http://emailrelay.sourceforge.net/.../emailrelay-src-1.1.1.tar.gz +Source: http://emailrelay.sourceforge.net/.../emailrelay-src-1.1.2.tar.gz BuildRoot: /tmp/emailrelay-install %define prefix /usr diff --git a/install-sh b/install-sh index e9de238..11870f1 100755 --- a/install-sh +++ b/install-sh @@ -109,7 +109,7 @@ then echo "install: no input file specified" exit 1 else - true + : fi if [ x"$dir_arg" != x ]; then @@ -120,7 +120,7 @@ if [ x"$dir_arg" != x ]; then instcmd=: chmodcmd="" else - instcmd=mkdir + instcmd=$mkdirprog fi else @@ -128,9 +128,9 @@ else # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. - if [ -f $src -o -d $src ] + if [ -f "$src" ] || [ -d "$src" ] then - true + : else echo "install: $src does not exist" exit 1 @@ -141,7 +141,7 @@ else echo "install: no destination specified" exit 1 else - true + : fi # If destination is a directory, append the input filename; if your system @@ -151,7 +151,7 @@ else then dst="$dst"/`basename $src` else - true + : fi fi @@ -163,8 +163,8 @@ dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` # Skip lots of stat calls in the usual case. if [ ! -d "$dstdir" ]; then -defaultIFS=' -' +defaultIFS=' + ' IFS="${IFS-${defaultIFS}}" oIFS="${IFS}" @@ -183,7 +183,7 @@ while [ $# -ne 0 ] ; do then $mkdirprog "${pathcomp}" else - true + : fi pathcomp="${pathcomp}/" @@ -194,10 +194,10 @@ if [ x"$dir_arg" != x ] then $doit $instcmd $dst && - if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && - if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && - if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && - if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else : ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else : ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else : ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else : ; fi else # If we're going to rename the final executable, determine the name now. @@ -216,7 +216,7 @@ else then dstfile=`basename $dst` else - true + : fi # Make a temp file name in the proper directory. @@ -235,10 +235,10 @@ else # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $instcmd $src $dsttmp" command. - if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && - if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && - if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && - if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else :;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else :;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else :;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else :;fi && # Now rename the file to the real destination. diff --git a/missing b/missing index 0a7fb5a..6a37006 100755 --- a/missing +++ b/missing @@ -1,6 +1,6 @@ #! /bin/sh # Common stub for a few missing GNU programs while installing. -# Copyright 1996, 1997, 1999, 2000 Free Software Foundation, Inc. +# Copyright (C) 1996, 1997, 1999, 2000, 2002 Free Software Foundation, Inc. # Originally by Fran,cois Pinard , 1996. # This program is free software; you can redistribute it and/or modify @@ -78,7 +78,7 @@ Supported PROGRAM values: ;; -v|--v|--ve|--ver|--vers|--versi|--versio|--version) - echo "missing 0.3 - GNU automake" + echo "missing 0.4 - GNU automake" ;; -*) @@ -87,7 +87,12 @@ Supported PROGRAM values: exit 1 ;; - aclocal) + aclocal*) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`acinclude.m4' or \`${configure_ac}'. You might want @@ -97,6 +102,11 @@ WARNING: \`$1' is missing on your system. You should only need it if ;; autoconf) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`${configure_ac}'. You might want to install the @@ -106,6 +116,11 @@ WARNING: \`$1' is missing on your system. You should only need it if ;; autoheader) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`acconfig.h' or \`${configure_ac}'. You might want @@ -124,7 +139,12 @@ WARNING: \`$1' is missing on your system. You should only need it if touch $touch_files ;; - automake) + automake*) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'. @@ -135,6 +155,34 @@ WARNING: \`$1' is missing on your system. You should only need it if while read f; do touch "$f"; done ;; + autom4te) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + + echo 1>&2 "\ +WARNING: \`$1' is needed, and you do not seem to have it handy on your + system. You might have modified some files without having the + proper tools for further handling them. + You can get \`$1Help2man' as part of \`Autoconf' from any GNU + archive site." + + file=`echo "$*" | sed -n 's/.*--output[ =]*\([^ ]*\).*/\1/p'` + test -z "$file" && file=`echo "$*" | sed -n 's/.*-o[ ]*\([^ ]*\).*/\1/p'` + if test -f "$file"; then + touch $file + else + test -z "$file" || exec >$file + echo "#! /bin/sh" + echo "# Created by GNU Automake missing as a replacement of" + echo "# $ $@" + echo "exit 0" + chmod +x $file + exit 1 + fi + ;; + bison|yacc) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if @@ -189,6 +237,11 @@ WARNING: \`$1' is missing on your system. You should only need it if ;; help2man) + if test -z "$run" && ($1 --version) > /dev/null 2>&1; then + # We have it, but it failed. + exit 1 + fi + echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a dependency of a manual page. You may need the @@ -240,23 +293,23 @@ WARNING: \`$1' is missing on your system. You should only need it if # Look for gnutar/gtar before invocation to avoid ugly error # messages. if (gnutar --version > /dev/null 2>&1); then - gnutar ${1+"$@"} && exit 0 + gnutar "$@" && exit 0 fi if (gtar --version > /dev/null 2>&1); then - gtar ${1+"$@"} && exit 0 + gtar "$@" && exit 0 fi firstarg="$1" if shift; then case "$firstarg" in *o*) firstarg=`echo "$firstarg" | sed s/o//` - tar "$firstarg" ${1+"$@"} && exit 0 + tar "$firstarg" "$@" && exit 0 ;; esac case "$firstarg" in *h*) firstarg=`echo "$firstarg" | sed s/h//` - tar "$firstarg" ${1+"$@"} && exit 0 + tar "$firstarg" "$@" && exit 0 ;; esac fi diff --git a/mkinstalldirs b/mkinstalldirs index 4f58503..8ab885e 100755 --- a/mkinstalldirs +++ b/mkinstalldirs @@ -4,9 +4,53 @@ # Created: 1993-05-16 # Public domain -# $Id: mkinstalldirs,v 1.13 1999/01/05 03:18:55 bje Exp $ - errstatus=0 +dirmode="" + +usage="\ +Usage: mkinstalldirs [-h] [--help] [-m mode] dir ..." + +# process command line arguments +while test $# -gt 0 ; do + case "${1}" in + -h | --help | --h* ) # -h for help + echo "${usage}" 1>&2; exit 0 ;; + -m ) # -m PERM arg + shift + test $# -eq 0 && { echo "${usage}" 1>&2; exit 1; } + dirmode="${1}" + shift ;; + -- ) shift; break ;; # stop option processing + -* ) echo "${usage}" 1>&2; exit 1 ;; # unknown option + * ) break ;; # first non-opt arg + esac +done + +for file +do + if test -d "$file"; then + shift + else + break + fi +done + +case $# in +0) exit 0 ;; +esac + +case $dirmode in +'') + if mkdir -p -- . 2>/dev/null; then + echo "mkdir -p -- $*" + exec mkdir -p -- "$@" + fi ;; +*) + if mkdir -m "$dirmode" -p -- . 2>/dev/null; then + echo "mkdir -m $dirmode -p -- $*" + exec mkdir -m "$dirmode" -p -- "$@" + fi ;; +esac for file do @@ -22,13 +66,24 @@ do esac if test ! -d "$pathcomp"; then - echo "mkdir $pathcomp" + echo "mkdir $pathcomp" - mkdir "$pathcomp" || lasterr=$? + mkdir "$pathcomp" || lasterr=$? - if test ! -d "$pathcomp"; then - errstatus=$lasterr - fi + if test ! -d "$pathcomp"; then + errstatus=$lasterr + else + if test ! -z "$dirmode"; then + echo "chmod $dirmode $pathcomp" + + lasterr="" + chmod "$dirmode" "$pathcomp" || lasterr=$? + + if test ! -z "$lasterr"; then + errstatus=$lasterr + fi + fi + fi fi pathcomp="$pathcomp/" @@ -37,4 +92,8 @@ done exit $errstatus +# Local Variables: +# mode: shell-script +# sh-indentation: 3 +# End: # mkinstalldirs ends here diff --git a/src/glib/garg.cpp b/src/glib/garg.cpp index 6d13d35..2c42f90 100644 --- a/src/glib/garg.cpp +++ b/src/glib/garg.cpp @@ -73,15 +73,51 @@ void G::Arg::setPrefix() void G::Arg::parse( HINSTANCE hinstance , const std::string & command_line ) { m_array.push_back( moduleName(hinstance) ) ; - G::Str::splitIntoTokens( command_line , m_array , " \t" ) ; + parseCore( command_line ) ; setPrefix() ; } void G::Arg::reparse( const std::string & command_line ) { - while( m_array.size() > 1U ) - m_array.pop_back() ; - G::Str::splitIntoTokens( command_line , m_array , " \t" ) ; + while( m_array.size() > 1U ) m_array.pop_back() ; + parseCore( command_line ) ; +} + +void G::Arg::parseCore( const std::string & command_line ) +{ + std::string s( command_line ) ; + protect( s ) ; + G::Str::splitIntoTokens( s , m_array , " " ) ; + unprotect( m_array ) ; +} + +void G::Arg::protect( std::string & s ) +{ + // replace all quoted spaces with a replacement + // (could do better: escaped quotes, tabs, single quotes) + G_DEBUG( "protect: before: " << Str::toPrintableAscii(s) ) ; + bool in_quote = false ; + const char quote = '"' ; + const char space = ' ' ; + const char replacement = '\0' ; + for( size_t pos = 0U ; pos < s.length() ; pos++ ) + { + if( s.at(pos) == quote ) in_quote = ! in_quote ; + if( in_quote && s.at(pos) == space ) s[pos] = replacement ; + } + G_DEBUG( "protect: after: " << Str::toPrintableAscii(s) ) ; +} + +void G::Arg::unprotect( StringArray & array ) +{ + // restore replacements to spaces + const char space = ' ' ; + const char replacement = '\0' ; + for( StringArray::iterator p = array.begin() ; p != array.end() ; ++p ) + { + std::string & s = *p ; + G::Str::replaceAll( s , std::string(1U,replacement) , std::string(1U,space) ) ; + } } bool G::Arg::contains( const std::string & sw , size_t sw_args , bool cs ) const diff --git a/src/glib/garg.h b/src/glib/garg.h index 2fe750b..9d6e9d8 100644 --- a/src/glib/garg.h +++ b/src/glib/garg.h @@ -120,6 +120,9 @@ private: bool find( bool , const std::string & , size_t , size_t * ) const ; void setPrefix() ; static bool match( bool , const std::string & , const std::string & ) ; + void parseCore( const std::string & ) ; + void protect( std::string & ) ; + void unprotect( StringArray & ) ; private: StringArray m_array ; diff --git a/src/glib/gdef.h b/src/glib/gdef.h index b007518..8468ba7 100644 --- a/src/glib/gdef.h +++ b/src/glib/gdef.h @@ -32,9 +32,13 @@ #ifndef G_DEF_H #define G_DEF_H - // Autoconf stuff + // Autoconf, part 1 // - #if defined(HAVE_CONFIG_H) + #if defined( HAVE_CONFIG_H ) + + #if ! defined( G_UNIX ) + #define G_UNIX + #endif #include @@ -47,29 +51,10 @@ #include #endif - #if ! HAVE_GMTIME_R - inline std::tm * gmtime_r( const std::time_t * tp , std::tm * tm_p ) - { - * tm_p = * std::gmtime( tp ) ; - return tm_p ; - } - #endif - - #if ! HAVE_LOCALTIME_R - inline std::tm * localtime_r( const std::time_t * tp , std::tm * tm_p ) - { - * tm_p = * std::localtime( tp ) ; - return tm_p ; - } - #endif - #if ! HAVE_SOCKLEN_T typedef int socklen_t ; #endif - #if ! defined( G_UNIX ) - #define G_UNIX - #endif #endif // Check operating-system switches @@ -111,6 +96,9 @@ #else #include #include + #if ! defined( HAVE_CONFIG_H ) + #include + #endif #endif // Define Windows-style types (only used for @@ -178,5 +166,32 @@ #define G_IGNORE (void) #endif + // Autoconf, part 2 + // + #if defined( HAVE_CONFIG_H ) + #if ! HAVE_GMTIME_R + inline std::tm * gmtime_r( const std::time_t * tp , std::tm * tm_p ) + { + * tm_p = * std::gmtime( tp ) ; + return tm_p ; + } + #endif + #if ! HAVE_LOCALTIME_R + inline std::tm * localtime_r( const std::time_t * tp , std::tm * tm_p ) + { + * tm_p = * std::localtime( tp ) ; + return tm_p ; + } + #endif + #if HAVE_SETGROUPS + #include + #else + inline int setgroups( size_t , const gid_t * ) + { + return 0 ; + } + #endif + #endif + #endif diff --git a/src/glib/gprocess.h b/src/glib/gprocess.h index ca672a2..a68b741 100644 --- a/src/glib/gprocess.h +++ b/src/glib/gprocess.h @@ -115,6 +115,10 @@ public: static int errno_() ; // Returns the process's current 'errno' value. + static void revokeExtraGroups() ; + // Revokes secondary group memberships if really root + // or if suid. + static Identity beOrdinary( Identity nobody , bool change_group = true ) ; // Revokes special privileges (root or suid). // diff --git a/src/glib/gprocess_unix.cpp b/src/glib/gprocess_unix.cpp index 703b9ed..acb2fb6 100644 --- a/src/glib/gprocess_unix.cpp +++ b/src/glib/gprocess_unix.cpp @@ -292,6 +292,15 @@ void G::Process::beSpecial( Identity identity , bool change_group ) if( change_group) setEffectiveGroupTo( identity , do_throw ) ; } +void G::Process::revokeExtraGroups() +{ + if( Identity::real().isRoot() || Identity::effective() != Identity::real() ) + { + gid_t dummy ; + G_IGNORE ::setgroups( 0U , &dummy ) ; // (only works for root, so ignore the return code) + } +} + G::Identity G::Process::beOrdinary( Identity nobody , bool change_group ) { Identity special_identity( Identity::effective() ) ; @@ -337,8 +346,11 @@ G::Process::Id::Id( const char * path ) : { // reentrant implementation suitable for a signal handler... int fd = ::open( path ? path : "" , O_RDONLY ) ; - char buffer[10] ; - ssize_t rc = ::read( fd , buffer , sizeof(buffer) ) ; + const size_t buffer_size = 11U ; + char buffer[buffer_size] ; + buffer[0U] = '\0' ; + ssize_t rc = ::read( fd , buffer , buffer_size - 1U ) ; + ::close( fd ) ; for( const char * p = buffer ; rc > 0 && *p >= '0' && *p <= '9' ; p++ , rc-- ) { m_pid *= 10 ; diff --git a/src/glib/gprocess_win32.cpp b/src/glib/gprocess_win32.cpp index a7ed3ae..9329022 100644 --- a/src/glib/gprocess_win32.cpp +++ b/src/glib/gprocess_win32.cpp @@ -36,6 +36,7 @@ namespace G { const int g_stderr_fileno = 2 ; const int g_sc_open_max = 256 ; // 32 in limits.h !? + const HANDLE HNULL = INVALID_HANDLE_VALUE ; class Pipe ; } ; @@ -43,14 +44,17 @@ class G::Pipe { public: G_EXCEPTION( Error , "pipe error" ) ; - explicit Pipe( bool active ) ; + explicit Pipe( bool active , bool do_throw = true ) ; ~Pipe() ; - int fd() const ; - std::string read() ; + HANDLE h() const ; + std::string read( bool do_throw = true ) ; +private: + static HANDLE create( HANDLE & h_write , bool do_throw ) ; + static HANDLE duplicate( HANDLE h , bool do_throw ) ; private: bool m_active ; - int m_fds[2] ; - int m_fd_writer ; + HANDLE m_read ; + HANDLE m_write ; } ; class G::Process::IdImp @@ -61,18 +65,20 @@ public: // === -G::Pipe::Pipe( bool active ) : +G::Pipe::Pipe( bool active , bool do_throw ) : m_active(active) , - m_fd_writer(-1) + m_read(HNULL) , + m_write(HNULL) { - m_fds[0] = m_fds[1] = -1 ; if( m_active ) { - int rc = ::_pipe( m_fds , 256 , _O_BINARY | _O_NOINHERIT ) ; - if( rc < 0 ) throw Error() ; - m_fd_writer = ::_dup( m_fds[1] ) ; // inherited - ::_close( m_fds[1] ) ; - m_fds[1] = -1 ; + const bool do_throw = true ; + HANDLE h = create( m_write , do_throw ) ; + if( h != HNULL ) + { + // dont let child processes inherit the read end + m_read = duplicate( h , do_throw ) ; + } } } @@ -80,25 +86,76 @@ G::Pipe::~Pipe() { if( m_active ) { - ::_close( m_fds[0] ) ; - ::_close( m_fds[1] ) ; - ::_close( m_fd_writer ) ; + if( m_read != HNULL ) ::CloseHandle( m_read ) ; + if( m_write != HNULL ) ::CloseHandle( m_write ) ; } } -int G::Pipe::fd() const +//static +HANDLE G::Pipe::create( HANDLE & h_write , bool do_throw ) { - return m_fd_writer ; + static SECURITY_ATTRIBUTES zero_attributes ; + SECURITY_ATTRIBUTES attributes( zero_attributes ) ; + attributes.nLength = sizeof(attributes) ; + attributes.lpSecurityDescriptor = NULL ; + attributes.bInheritHandle = TRUE ; + + HANDLE h_read = HNULL ; + h_write = HNULL ; + BOOL rc = ::CreatePipe( &h_read , &h_write , &attributes , 0 ) ; + if( rc == 0 ) + { + DWORD error = ::GetLastError() ; + G_ERROR( "G::Pipe::create: pipe error: create: " << error ) ; + if( do_throw ) throw Error( "create" ) ; + h_read = h_write = HNULL ; + } + return h_read ; } -std::string G::Pipe::read() +//static +HANDLE G::Pipe::duplicate( HANDLE h , bool do_throw ) +{ + HANDLE result = HNULL ; + BOOL rc = ::DuplicateHandle( GetCurrentProcess() , h , GetCurrentProcess() , + &result , 0 , FALSE , DUPLICATE_SAME_ACCESS ) ; + if( rc == 0 || result == HNULL ) + { + DWORD error = ::GetLastError() ; + ::CloseHandle( h ) ; + G_ERROR( "G::Pipe::duplicate: pipe error: dup: " << error ) ; + if( do_throw ) throw Error( "dup" ) ; + result = HNULL ; + } + ::CloseHandle( h ) ; + return result ; +} + +HANDLE G::Pipe::h() const +{ + return m_write ; +} + +std::string G::Pipe::read( bool do_throw ) { if( ! m_active ) return std::string() ; - ::_close( m_fd_writer ) ; + ::CloseHandle( m_write ) ; m_write = HNULL ; char buffer[4096] ; - int rc = m_fds[0] == -1 ? 0 : ::_read( m_fds[0] , buffer , sizeof(buffer) ) ; - if( rc < 0 ) throw Error() ; - return std::string( buffer , rc ) ; + DWORD n = sizeof(buffer) ; + DWORD m = 0UL ; + BOOL rc = ::ReadFile( m_read , buffer , n , &m , NULL ) ; + DWORD error = ::GetLastError() ; + ::CloseHandle( m_read ) ; m_read = HNULL ; + if( !rc ) + { + G_DEBUG( "G::Pipe::read: error: " << m << " byte(s): error=" << error ) ; + if( error != ERROR_BROKEN_PIPE ) + { + G_ERROR( "G::Pipe::read: pipe read error: " << error ) ; + if( do_throw ) throw Error( "read" ) ; + } + } + return std::string( buffer , m ) ; } // === @@ -173,38 +230,64 @@ int G::Process::errno_() return errno ; } -int G::Process::spawn( Identity , const Path & exe , const Strings & args_ , +int G::Process::spawn( Identity , const Path & exe , const Strings & args , std::string * pipe_result_p , int error_return ) { - // open file descriptors are inherited across ::_spawn() -- - // no fcntl() is available to set close-on-exec -- but see - // also ::CreateProcess() - - Strings args( args_ ) ; // non-const copy - Pipe pipe( pipe_result_p != NULL ) ; - if( pipe_result_p != NULL ) - args.push_front( Str::fromInt(pipe.fd()) ) ; // kludge -- child must write on fd passed as argv[1] - G_DEBUG( "G::Process::spawn: \"" << exe << "\": \"" << Str::join(args,"\",\"") << "\"" ) ; - char ** argv = new char* [ args.size() + 2U ] ; - argv[0U] = const_cast( exe.pathCstr() ) ; - unsigned int argc = 1U ; - for( Strings::const_iterator arg_p = args.begin() ; arg_p != args.end() ; ++arg_p , argc++ ) - argv[argc] = const_cast(arg_p->c_str()) ; - argv[argc] = NULL ; + std::string command_line = std::string("\"") + exe.str() + "\"" ; + for( Strings::const_iterator arg_p = args.begin() ; arg_p != args.end() ; ++arg_p ) + { + std::string arg = *arg_p ; + if( arg.find(" ") != std::string::npos && arg.find("\"") != 0U ) + arg = std::string("\"") + arg + "\"" ; + command_line += ( std::string(" ") + arg ) ; + } - const int mode = _P_WAIT ; - ::_flushall() ; - int rc = ::_spawnv( mode , exe.str().c_str() , argv ) ; - int error = errno_() ; - delete [] argv ; - G_DEBUG( "G::Process::spawn: _spawnv(): rc=" << rc << ": errno=" << error ) ; + Pipe pipe( pipe_result_p != NULL ) ; - if( pipe_result_p != NULL ) - *pipe_result_p = pipe.read() ; + SECURITY_ATTRIBUTES * process_attributes = NULL ; + SECURITY_ATTRIBUTES * thread_attributes = NULL ; + BOOL inherit = TRUE ; + DWORD flags = CREATE_NO_WINDOW ; + LPVOID env = NULL ; + LPCTSTR cwd = NULL ; + static STARTUPINFO zero_start ; + STARTUPINFO start(zero_start) ; + start.cb = sizeof(start) ; + start.dwFlags = STARTF_USESTDHANDLES ; + start.hStdInput = HNULL ; + start.hStdOutput = pipe.h() ; + start.hStdError = HNULL ; + PROCESS_INFORMATION info ; + char * command_line_p = const_cast(command_line.c_str()) ; + BOOL rc = ::CreateProcess( exe.str().c_str() , command_line_p , + process_attributes , thread_attributes , inherit , + flags , env , cwd , &start , &info ) ; - return rc < 0 ? error_return : rc ; + bool ok = !!rc ; + DWORD exit_code = error_return ; + if( !ok ) + { + DWORD e = ::GetLastError() ; + G_ERROR( "G::Process::spawn: create-process error " << e << ": " << command_line ) ; + } + else + { + DWORD timeout_ms = 30000UL ; + if( WAIT_TIMEOUT == ::WaitForSingleObject( info.hProcess , timeout_ms ) ) + { + G_ERROR( "G::Process::spawn: child process has not terminated: still waiting" ) ; + ::WaitForSingleObject( info.hProcess , INFINITE ) ; + } + ::GetExitCodeProcess( info.hProcess , &exit_code ) ; + G_LOG( "G::Process::spawn: exit " << exit_code << " from \"" << exe << "\": " << exit_code ) ; + + if( pipe_result_p != NULL ) + *pipe_result_p = pipe.read(false) ; + } + + return exit_code ; } G::Identity G::Process::beOrdinary( Identity identity , bool ) @@ -218,6 +301,11 @@ void G::Process::beSpecial( Identity , bool ) // not implemented } +void G::Process::revokeExtraGroups() +{ + // not implemented +} + // not implemented... // Who G::Process::fork() {} // Who G::Process::fork( Id & child ) {} diff --git a/src/glib/groot.cpp b/src/glib/groot.cpp index 24edb91..52a8b39 100644 --- a/src/glib/groot.cpp +++ b/src/glib/groot.cpp @@ -63,6 +63,7 @@ G::Root::~Root() //static void G::Root::init( const std::string & nobody ) { + Process::revokeExtraGroups() ; m_nobody = nobody.empty() ? Identity::invalid() : Identity(nobody) ; m_special = Process::beOrdinary( m_nobody ) ; } diff --git a/src/glib/md5.h b/src/glib/md5.h index ba24f1a..fa13f22 100644 --- a/src/glib/md5.h +++ b/src/glib/md5.h @@ -1,22 +1,3 @@ -// -// Copyright (C) 2001-2003 Graeme Walker -// -// 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., 675 Mass Ave, Cambridge, MA 02139, USA. -// -// === /* MD5.H - header file for MD5C.C */ diff --git a/src/glib/md5c.c b/src/glib/md5c.c index 77a628b..8cd102b 100644 --- a/src/glib/md5c.c +++ b/src/glib/md5c.c @@ -1,23 +1,3 @@ -/* - Copyright (C) 2001-2003 Graeme Walker - - 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - /* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm */ diff --git a/src/gnet/gclient.cpp b/src/gnet/gclient.cpp index 1fd2201..e4974a3 100644 --- a/src/gnet/gclient.cpp +++ b/src/gnet/gclient.cpp @@ -39,6 +39,7 @@ namespace const int c_retries = 10 ; // number of retries when using a priviledged local port number const int c_port_start = 512 ; const int c_port_end = 1024 ; + const std::string c_cannot_connect_to( "cannot connect to " ) ; } namespace GNet @@ -73,7 +74,7 @@ GNet::ClientResolver::ClientResolver( ClientImp & imp ) : // === // Class: GNet::ClientImp -// Description: A pimple-pattern implementation class for GClient. +// Description: A pimple-pattern implementation class for GNet::Client. // class GNet::ClientImp : public GNet::EventHandler { @@ -174,6 +175,12 @@ std::string GNet::Client::peerName() const return m_imp->peerName() ; } +//static +bool GNet::Client::canRetry( const std::string & error ) +{ + return error.find( c_cannot_connect_to ) == 0U ; +} + // === bool GNet::ClientImp::m_first = true ; @@ -269,7 +276,7 @@ bool GNet::ClientImp::connect( std::string host , std::string service , } if( immediate ) { - G_WARNING( "GNet::Client::connect: immediate connection" ) ; // delete soon + G_DEBUG( "GNet::Client::connect: immediate connection" ) ; // delete soon s().addReadHandler( *this ) ; s().addExceptionHandler( *this ) ; setState( Connected ) ; @@ -392,7 +399,7 @@ GNet::ClientImp::Status GNet::ClientImp::connectCore( Address remote_address , if( !s().connect( remote_address , &immediate ) ) { G_DEBUG( "GNet::ClientImp::connect: immediate failure" ) ; - error = "cannot connect to " ; + error = c_cannot_connect_to ; error.append( remote_address.displayString().c_str() ) ; // we should return Failure here, but Microsoft's stack @@ -433,7 +440,7 @@ void GNet::ClientImp::writeEvent() } else if( m_state == Connecting ) { - std::string message( "cannot connect to " ) ; + std::string message( c_cannot_connect_to ) ; message.append( m_address.displayString().c_str() ) ; setState( Failed ) ; close() ; diff --git a/src/gnet/gclient.h b/src/gnet/gclient.h index 98ee608..98fb3f5 100644 --- a/src/gnet/gclient.h +++ b/src/gnet/gclient.h @@ -100,24 +100,44 @@ public: // Returns the peer's canonical name if available. // Returns the empty string if not. + static bool canRetry( const std::string & error_string ) ; + // Parses the error string from onError(), retuning true + // if it might be temporary. In practice it returns + // true iff Socket::connect() failed. + protected: friend class ClientImp ; virtual void onConnect( Socket & socket ) = 0 ; // Called once connected. May (unfortunately) be // called from within connect(). + // + // Precondition: !connected() + // Postcondition: connected() virtual void onDisconnect() = 0 ; // Called when disconnected by the peer. + // + // Precondition: connected() + // Postcondition: !connected() virtual void onData( const char * data , size_t size ) = 0 ; // Called on receipt of data. + // + // Precondition: connected() + // Postcondition: connected() - virtual void onError( const std::string &error ) = 0 ; - // Called when an asyncronous, and fatal, error occurs. + virtual void onError( const std::string & error ) = 0 ; + // Called when a connect request fails. + // + // Precondition: !connected() + // Postcondition: !connected() virtual void onWriteable() = 0 ; // Called when a blocked connection become writeable. + // + // Precondition: connected() + // Postcondition: connected() private: Client( const Client& ) ; // Copy constructor. Not implemented. diff --git a/src/gnet/gserver.cpp b/src/gnet/gserver.cpp index 5461265..b938716 100644 --- a/src/gnet/gserver.cpp +++ b/src/gnet/gserver.cpp @@ -144,6 +144,17 @@ void GNet::Server::init( const Address & listening_address ) m_socket->addReadHandler( *this ) ; } +//static +bool GNet::Server::canBind( const Address & address , bool do_throw ) +{ + G::Root claim_root ; + StreamSocket s ; + bool ok = s.canBindHint( address ) ; + if( !ok && do_throw ) + throw CannotBind( address.displayString() ) ; + return ok ; +} + GNet::Server::~Server() { serverCleanup() ; diff --git a/src/gnet/gserver.h b/src/gnet/gserver.h index 0a966bc..cc87232 100644 --- a/src/gnet/gserver.h +++ b/src/gnet/gserver.h @@ -92,6 +92,11 @@ public: PeerInfo() ; } ; + static bool canBind( const Address & listening_address , bool do_throw ) ; + // Checks that the specified address can be + // bound. Throws CannotBind if the address cannot + // be bound and 'do_throw' is true. + explicit Server( unsigned int listening_port ) ; // Constructor taking a port number. The server // listens on all local interfaces. diff --git a/src/gnet/gsocket.h b/src/gnet/gsocket.h index d281979..2614972 100644 --- a/src/gnet/gsocket.h +++ b/src/gnet/gsocket.h @@ -94,11 +94,19 @@ public: // address. This is used for listening // sockets. - bool bind(); + bool bind() ; // Binds the socket with an INADDR_ANY network address // and a zero port number. This is used to // initialise the socket prior to connect(). + bool canBindHint( const Address & address ) ; + // Returns true if the socket can probably be + // bound with the given address. Some + // implementations will always return + // true. This method should be used on a + // temporary socket of the correct dynamic + // type. + bool connect( const Address &addr , bool *done = NULL ) ; // Initiates a connection to (or association // with) the given address. Returns false on diff --git a/src/gnet/gsocket_unix.cpp b/src/gnet/gsocket_unix.cpp index 26389ea..7d02f35 100644 --- a/src/gnet/gsocket_unix.cpp +++ b/src/gnet/gsocket_unix.cpp @@ -18,7 +18,7 @@ // // === // -// gsocket_unix.cc +// gsocket_unix.cpp // #include "gdef.h" @@ -59,7 +59,7 @@ int GNet::Socket::reason() void GNet::Socket::doClose() { G_ASSERT( valid() ) ; - ::close( m_socket ); + ::close( m_socket ) ; m_socket = -1; } @@ -93,3 +93,10 @@ void GNet::Socket::setFault() m_reason = EFAULT ; } +bool GNet::Socket::canBindHint( const Address & address ) +{ + bool can_bind = bind( address ) ; + close() ; + return can_bind ; +} + diff --git a/src/gnet/gsocket_win32.cpp b/src/gnet/gsocket_win32.cpp index f7439ec..cdf9f26 100644 --- a/src/gnet/gsocket_win32.cpp +++ b/src/gnet/gsocket_win32.cpp @@ -91,3 +91,9 @@ void GNet::Socket::setFault() m_reason = WSAEFAULT ; } +bool GNet::Socket::canBindHint( const Address & ) +{ + // rebinding the same port number fails, so a dummy implementation here + return true ; +} + diff --git a/src/gsmtp/Makefile.am b/src/gsmtp/Makefile.am index f33edde..7ab6fd5 100644 --- a/src/gsmtp/Makefile.am +++ b/src/gsmtp/Makefile.am @@ -47,10 +47,14 @@ libgsmtp_a_SOURCES = \ gprotocolmessage.h \ gprotocolmessageforward.cpp \ gprotocolmessageforward.h \ + gprotocolmessagescanner.cpp \ + gprotocolmessagescanner.h \ gprotocolmessagestore.cpp \ gprotocolmessagestore.h \ gsasl_native.cpp \ gsasl.h \ + gscannerclient.cpp \ + gscannerclient.h \ gsecrets.cpp \ gsecrets.h \ gserverprotocol.cpp \ diff --git a/src/gsmtp/Makefile.in b/src/gsmtp/Makefile.in index 07ddbac..2e8a6ac 100644 --- a/src/gsmtp/Makefile.in +++ b/src/gsmtp/Makefile.in @@ -123,10 +123,14 @@ libgsmtp_a_SOURCES = \ gprotocolmessage.h \ gprotocolmessageforward.cpp \ gprotocolmessageforward.h \ + gprotocolmessagescanner.cpp \ + gprotocolmessagescanner.h \ gprotocolmessagestore.cpp \ gprotocolmessagestore.h \ gsasl_native.cpp \ gsasl.h \ + gscannerclient.cpp \ + gscannerclient.h \ gsecrets.cpp \ gsecrets.h \ gserverprotocol.cpp \ @@ -158,11 +162,12 @@ am_libgsmtp_a_OBJECTS = gadminserver.$(OBJEXT) gbase64.$(OBJEXT) \ gmessagestore.$(OBJEXT) gmessagestore_unix.$(OBJEXT) \ gnewfile.$(OBJEXT) gnewmessage.$(OBJEXT) \ gprotocolmessage.$(OBJEXT) gprotocolmessageforward.$(OBJEXT) \ + gprotocolmessagescanner.$(OBJEXT) \ gprotocolmessagestore.$(OBJEXT) gsasl_native.$(OBJEXT) \ - gsecrets.$(OBJEXT) gserverprotocol.$(OBJEXT) \ - gsmtpclient.$(OBJEXT) gsmtpserver.$(OBJEXT) \ - gstoredfile.$(OBJEXT) gstoredmessage.$(OBJEXT) \ - gverifier.$(OBJEXT) gxtext.$(OBJEXT) + gscannerclient.$(OBJEXT) gsecrets.$(OBJEXT) \ + gserverprotocol.$(OBJEXT) gsmtpclient.$(OBJEXT) \ + gsmtpserver.$(OBJEXT) gstoredfile.$(OBJEXT) \ + gstoredmessage.$(OBJEXT) gverifier.$(OBJEXT) gxtext.$(OBJEXT) libgsmtp_a_OBJECTS = $(am_libgsmtp_a_OBJECTS) DEFS = @DEFS@ @@ -181,8 +186,11 @@ am__depfiles_maybe = depfiles @AMDEP_TRUE@ ./$(DEPDIR)/gnewfile.Po ./$(DEPDIR)/gnewmessage.Po \ @AMDEP_TRUE@ ./$(DEPDIR)/gprotocolmessage.Po \ @AMDEP_TRUE@ ./$(DEPDIR)/gprotocolmessageforward.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/gprotocolmessagescanner.Po \ @AMDEP_TRUE@ ./$(DEPDIR)/gprotocolmessagestore.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/gsasl_native.Po ./$(DEPDIR)/gsecrets.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/gsasl_native.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/gscannerclient.Po \ +@AMDEP_TRUE@ ./$(DEPDIR)/gsecrets.Po \ @AMDEP_TRUE@ ./$(DEPDIR)/gserverprotocol.Po \ @AMDEP_TRUE@ ./$(DEPDIR)/gsmtpclient.Po \ @AMDEP_TRUE@ ./$(DEPDIR)/gsmtpserver.Po \ @@ -237,8 +245,10 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gnewmessage.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprotocolmessage.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprotocolmessageforward.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprotocolmessagescanner.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gprotocolmessagestore.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsasl_native.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gscannerclient.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsecrets.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gserverprotocol.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gsmtpclient.Po@am__quote@ diff --git a/src/gsmtp/gclientprotocol.cpp b/src/gsmtp/gclientprotocol.cpp index 6f9f132..a210f11 100644 --- a/src/gsmtp/gclientprotocol.cpp +++ b/src/gsmtp/gclientprotocol.cpp @@ -37,7 +37,7 @@ #include "gassert.h" GSmtp::ClientProtocol::ClientProtocol( Sender & sender , const Secrets & secrets , - const std::string & thishost_name , unsigned int timeout , bool must_authenticate ) : + const std::string & thishost_name , unsigned int timeout , bool must_authenticate , bool strict ) : m_sender(sender) , m_secrets(secrets) , m_thishost(thishost_name) , @@ -47,6 +47,8 @@ GSmtp::ClientProtocol::ClientProtocol( Sender & sender , const Secrets & secrets m_message_is_8bit(false) , m_authenticated_with_server(false) , m_must_authenticate(must_authenticate) , + m_strict(strict) , + m_warned(false) , m_timeout(timeout) , m_signalled(false) { @@ -147,7 +149,8 @@ void GSmtp::ClientProtocol::apply( const std::string & rx ) void GSmtp::ClientProtocol::sendMail() { - if( !m_server_has_8bitmime && m_message_is_8bit ) + const bool dodgy = m_message_is_8bit && !m_server_has_8bitmime ; + if( dodgy && m_strict ) { std::string reason = "cannot send 8-bit message to 7-bit server" ; G_WARNING( "GSmtp::ClientProtocol: " << reason ) ; @@ -156,6 +159,12 @@ void GSmtp::ClientProtocol::sendMail() } else { + if( dodgy && !m_warned ) + { + m_warned = true ; + G_WARNING( "GSmtp::ClientProtocol::sendMail: sending an eight-bit message " + "to a server which has not advertised the 8BITMIME extension" ) ; + } sendMailCore() ; } } diff --git a/src/gsmtp/gclientprotocol.h b/src/gsmtp/gclientprotocol.h index 2a34eb2..68844b3 100644 --- a/src/gsmtp/gclientprotocol.h +++ b/src/gsmtp/gclientprotocol.h @@ -159,7 +159,8 @@ public: ClientProtocol( Sender & sender , const Secrets & secrets , const std::string & thishost_name , - unsigned int timeout , bool must_authenticate ) ; + unsigned int timeout , bool must_authenticate , + bool eight_bit_strict = false ) ; // Constructor. The 'sender' and 'secrets' references // are kept. // @@ -168,6 +169,10 @@ public: // // The 'thishost_name' parameter is used in the // SMTP EHLO request. + // + // If the 'eight-bit-strict' flag is true then + // an eight-bit message being sent to a + // seven-bit server will be failed. G::Signal3 & doneSignal() ; // Returns a signal which is raised once the protocol has @@ -237,6 +242,8 @@ private: std::string m_auth_mechanism ; std::auto_ptr m_sasl ; bool m_must_authenticate ; + bool m_strict ; + bool m_warned ; unsigned int m_timeout ; G::Signal3 m_signal ; bool m_signalled ; diff --git a/src/gsmtp/gnewfile.cpp b/src/gsmtp/gnewfile.cpp index 10872b2..5f95f00 100644 --- a/src/gsmtp/gnewfile.cpp +++ b/src/gsmtp/gnewfile.cpp @@ -27,6 +27,7 @@ #include "gnewfile.h" #include "gmemory.h" #include "gprocess.h" +#include "gstr.h" #include "groot.h" #include "gfile.h" #include "gxtext.h" @@ -119,9 +120,10 @@ bool GSmtp::NewFile::store( const std::string & auth_id , const std::string & cl bool cancelled = false ; if( ok ) { - ok = preprocess( m_content_path , cancelled ) ; + std::string output ; + ok = preprocess( m_content_path , cancelled , output ) ; if( !ok ) - reason = "pre-processing failed" ; + reason = output.empty() ? std::string("pre-processing failed") : output ; } G_ASSERT( !(ok&&cancelled) ) ; @@ -165,11 +167,11 @@ void GSmtp::NewFile::cleanup() } } -bool GSmtp::NewFile::preprocess( const G::Path & path , bool & cancelled ) +bool GSmtp::NewFile::preprocess( const G::Path & path , bool & cancelled , std::string & output ) { if( ! m_preprocess ) return true ; - int exit_code = preprocessCore( path ) ; + int exit_code = preprocessCore( path , output ) ; bool is_ok = exit_code == 0 ; bool is_special = exit_code >= 100 && exit_code <= 107 ; @@ -199,16 +201,43 @@ bool GSmtp::NewFile::preprocess( const G::Path & path , bool & cancelled ) } } -int GSmtp::NewFile::preprocessCore( const G::Path & path ) +int GSmtp::NewFile::preprocessCore( const G::Path & path , std::string & output ) { G_LOG( "GSmtp::NewFile::preprocess: " << m_preprocessor << " " << path ) ; G::Strings args ; args.push_back( path.str() ) ; - int exit_code = G::Process::spawn( G::Root::nobody() , m_preprocessor , args ) ; - G_LOG( "GSmtp::NewFile::preprocess: exit status " << exit_code ) ; + std::string raw_output ; + int exit_code = G::Process::spawn( G::Root::nobody() , m_preprocessor , args , &raw_output ) ; + output = parseOutput( raw_output ) ; + G_LOG( "GSmtp::NewFile::preprocess: exit status " << exit_code << " (\"" << output << "\")" ) ; return exit_code ; } +std::string GSmtp::NewFile::parseOutput( std::string s ) const +{ + G_DEBUG( "GSmtp::NewFile::parseOutput: in: \"" << G::Str::toPrintableAscii(s) << "\"" ) ; + const std::string start("<<") ; + const std::string end(">>") ; + std::string result ; + G::Str::replaceAll( s , "\r\n" , "\n" ) ; + G::Str::replaceAll( s , "\r" , "\n" ) ; + G::Strings lines ; + G::Str::splitIntoFields( s , lines , "\n" ) ; + for( G::Strings::iterator p = lines.begin() ; p != lines.end() ; ++p ) + { + std::string line = *p ; + size_t pos_start = line.find(start) ; + size_t pos_end = line.find(end) ; + if( pos_start == 0U && pos_end != std::string::npos ) + { + result = G::Str::toPrintableAscii(line.substr(start.length(),pos_end-start.length())) ; + } + } + G_DEBUG( "GSmtp::NewFile::parseOutput: in: \"" << G::Str::toPrintableAscii(result) << "\"" ) ; + return result ; +} + + void GSmtp::NewFile::deliver( const G::Strings & /*to*/ , const G::Path & content_path , const G::Path & envelope_path_now , const G::Path & envelope_path_later ) @@ -263,6 +292,11 @@ unsigned long GSmtp::NewFile::id() const return m_seq ; } +G::Path GSmtp::NewFile::contentPath() const +{ + return m_content_path ; +} + void GSmtp::NewFile::setPreprocessor( const G::Path & exe ) { if( exe.isRelative() ) diff --git a/src/gsmtp/gnewfile.h b/src/gsmtp/gnewfile.h index 02fb366..420419a 100644 --- a/src/gsmtp/gnewfile.h +++ b/src/gsmtp/gnewfile.h @@ -70,6 +70,9 @@ public: // Defines a program which is used for pre-processing // messages before they are stored. + G::Path contentPath() const ; + // Returns the path of the content file. + private: FileStore & m_store ; unsigned long m_seq ; @@ -90,8 +93,9 @@ private: const std::string & crlf() const ; static bool isEightBit( const std::string & line ) ; void deliver( const G::Strings & , const G::Path & , const G::Path & , const G::Path & ) ; - bool preprocess( const G::Path & , bool & ) ; - int preprocessCore( const G::Path & ) ; + bool preprocess( const G::Path & , bool & , std::string & ) ; + int preprocessCore( const G::Path & , std::string & ) ; + std::string parseOutput( std::string ) const ; bool commit( const G::Path & , const G::Path & ) ; void rollback() ; void cleanup() ; diff --git a/src/gsmtp/gprotocolmessage.h b/src/gsmtp/gprotocolmessage.h index 30ac1a2..df5455a 100644 --- a/src/gsmtp/gprotocolmessage.h +++ b/src/gsmtp/gprotocolmessage.h @@ -61,6 +61,13 @@ public: // As a special case, if success is true and id is zero then // the message processing was cancelled. + virtual G::Signal3 & preparedSignal() = 0 ; + // Returns a signal which is raised once prepare() has + // completed. + // + // The signal parameters are 'success', 'temporary' and + // 'reason'. + virtual void clear() = 0 ; // Clears the message state and terminates // any asynchronous message processing. @@ -69,6 +76,13 @@ public: // Sets the message envelope 'from'. // Returns false if an invalid user. + virtual bool prepare() = 0 ; + // Called to start any asynchronous preparation which is + // required after setFrom(). Returns true if there + // is something to do (in which case preparedSignal() + // must be fired later), or false if there is nothing + // to do. + virtual bool addTo( const std::string & to_user , Verifier::Status to_status ) = 0 ; // Adds an envelope 'to'. // diff --git a/src/gsmtp/gprotocolmessageforward.cpp b/src/gsmtp/gprotocolmessageforward.cpp index 91ebe1f..c135019 100644 --- a/src/gsmtp/gprotocolmessageforward.cpp +++ b/src/gsmtp/gprotocolmessageforward.cpp @@ -44,13 +44,23 @@ GSmtp::ProtocolMessageForward::ProtocolMessageForward( MessageStore & store , m_pm.doneSignal().connect( G::slot(*this,&ProtocolMessageForward::processDone) ) ; } +G::Signal3 & GSmtp::ProtocolMessageForward::storageDoneSignal() +{ + return m_pm.doneSignal() ; +} + GSmtp::ProtocolMessageForward::~ProtocolMessageForward() { } G::Signal3 & GSmtp::ProtocolMessageForward::doneSignal() { - return m_signal ; + return m_done_signal ; +} + +G::Signal3 & GSmtp::ProtocolMessageForward::preparedSignal() +{ + return m_prepared_signal ; } void GSmtp::ProtocolMessageForward::clear() @@ -64,6 +74,11 @@ bool GSmtp::ProtocolMessageForward::setFrom( const std::string & from ) return m_pm.setFrom( from ) ; } +bool GSmtp::ProtocolMessageForward::prepare() +{ + return false ; // no async preparation required +} + bool GSmtp::ProtocolMessageForward::addTo( const std::string & to , Verifier::Status to_status ) { return m_pm.addTo( to , to_status ) ; @@ -90,19 +105,24 @@ void GSmtp::ProtocolMessageForward::process( const std::string & auth_id , m_pm.process( auth_id , client_ip ) ; } -void GSmtp::ProtocolMessageForward::processDone( bool success , unsigned long id , std::string reason_in ) +void GSmtp::ProtocolMessageForward::processDone( bool success , unsigned long id , std::string reason ) { - std::string reason( reason_in ) ; - bool nothing_to_do = success && id == 0UL ; if( success && id != 0UL ) { m_id = id ; - success = forward( id , nothing_to_do , &reason ) ; - } - if( nothing_to_do || !success ) + bool nothing_to_do = false ; + success = forward( id , nothing_to_do , &reason ) ; + if( !success || nothing_to_do ) + { + // failed or no recipients + m_done_signal.emit( success , id , reason ) ; + } + } + else { - m_signal.emit( success , id , reason ) ; + // failed or cancelled + m_done_signal.emit( success , id , reason ) ; } } @@ -131,14 +151,20 @@ bool GSmtp::ProtocolMessageForward::forward( unsigned long id , bool & nothing_t ok = reason.empty() ; if( !ok && reason_p != NULL ) + { + G_DEBUG( "GSmtp::ProtocolMessageForward::forward: client connect error" ) ; *reason_p = reason ; + } } return ok ; } catch( std::exception & e ) { if( reason_p != NULL ) + { + G_DEBUG( "GSmtp::ProtocolMessageForward::forward: exception" ) ; *reason_p = e.what() ; + } return false ; } } @@ -147,6 +173,6 @@ void GSmtp::ProtocolMessageForward::clientDone( std::string reason ) { G_DEBUG( "GSmtp::ProtocolMessageForward::clientDone: \"" << reason << "\"" ) ; const bool ok = reason.empty() ; - m_signal.emit( ok , m_id , reason ) ; + m_done_signal.emit( ok , m_id , reason ) ; } diff --git a/src/gsmtp/gprotocolmessageforward.h b/src/gsmtp/gprotocolmessageforward.h index 26bcaf5..44f97a0 100644 --- a/src/gsmtp/gprotocolmessageforward.h +++ b/src/gsmtp/gprotocolmessageforward.h @@ -66,12 +66,18 @@ public: virtual G::Signal3 & doneSignal() ; // See ProtocolMessage. + virtual G::Signal3 & preparedSignal() ; + // See ProtocolMessage. + virtual void clear() ; // See ProtocolMessage. virtual bool setFrom( const std::string & from_user ) ; // See ProtocolMessage. + virtual bool prepare() ; + // See ProtocolMessage. + virtual bool addTo( const std::string & to_user , Verifier::Status to_status ) ; // See ProtocolMessage. @@ -87,9 +93,16 @@ public: virtual void process( const std::string & auth_id , const std::string & client_ip ) ; // See ProtocolMessage. +protected: + G::Signal3 & storageDoneSignal() ; + // Returns the signal which is used to signal that the storage + // is complete. + + void processDone( bool , unsigned long , std::string ) ; + // ... + private: void operator=( const ProtocolMessageForward & ) ; // not implemented - void processDone( bool , unsigned long , std::string ) ; // ProtocolMessage::doneSignal() void clientDone( std::string ) ; // Client::doneSignal() bool forward( unsigned long , bool & , std::string * ) ; @@ -102,7 +115,8 @@ private: unsigned long m_id ; unsigned int m_response_timeout ; unsigned int m_connection_timeout ; - G::Signal3 m_signal ; + G::Signal3 m_done_signal ; + G::Signal3 m_prepared_signal ; } ; #endif diff --git a/src/gsmtp/gprotocolmessagescanner.cpp b/src/gsmtp/gprotocolmessagescanner.cpp new file mode 100644 index 0000000..3f5408e --- /dev/null +++ b/src/gsmtp/gprotocolmessagescanner.cpp @@ -0,0 +1,101 @@ +// +// Copyright (C) 2001-2003 Graeme Walker +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// === +// +// gprotocolmessagescanner.cpp +// + +#include "gdef.h" +#include "gsmtp.h" +#include "gprotocolmessagescanner.h" +#include "gmessagestore.h" +#include "gfilestore.h" +#include "gmemory.h" +#include "gstr.h" +#include "gassert.h" +#include "glog.h" + +GSmtp::ProtocolMessageScanner::ProtocolMessageScanner( MessageStore & store , + const Secrets & client_secrets , + const std::string & smtp_server , + unsigned int smtp_response_timeout , unsigned int smtp_connection_timeout , + const std::string & scanner_server , + unsigned int scanner_response_timeout , unsigned int scanner_connection_timeout ) : + ProtocolMessageForward(store,client_secrets,smtp_server,smtp_response_timeout,smtp_connection_timeout), + m_store(store) , + m_scanner_server(scanner_server) , + m_scanner_response_timeout(scanner_response_timeout) , + m_scanner_connection_timeout(scanner_connection_timeout) , + m_id(0UL) +{ + G_DEBUG( "GSmtp::ProtocolMessageScanner::ctor" ) ; + scannerInit() ; + ProtocolMessageForward::storageDoneSignal().disconnect() ; + ProtocolMessageForward::storageDoneSignal().connect( G::slot(*this,&ProtocolMessageScanner::storageDone) ) ; +} + +void GSmtp::ProtocolMessageScanner::scannerInit() +{ + m_scanner_client <<= + new ScannerClient(m_scanner_server,m_scanner_connection_timeout,m_scanner_response_timeout) ; + m_scanner_client->connectedSignal().connect( G::slot(*this,&ProtocolMessageScanner::connectDone) ) ; + m_scanner_client->doneSignal().connect( G::slot(*this,&ProtocolMessageScanner::scannerDone) ) ; +} + +GSmtp::ProtocolMessageScanner::~ProtocolMessageScanner() +{ +} + +G::Signal3 & GSmtp::ProtocolMessageScanner::preparedSignal() +{ + return m_prepared_signal ; +} + +bool GSmtp::ProtocolMessageScanner::prepare() +{ + m_scanner_client->startConnecting() ; + return true ; +} + +void GSmtp::ProtocolMessageScanner::connectDone( std::string reason , bool temporary_error ) +{ + G_DEBUG( "GSmtp::ProtocolMessageScanner::connectDone: \"" << reason << "\", " << temporary_error ) ; + m_prepared_signal.emit( reason.empty() , temporary_error , reason ) ; +} + +void GSmtp::ProtocolMessageScanner::storageDone( bool , unsigned long id , std::string ) +{ + G_DEBUG( "GSmtp::ProtocolMessageScanner::storageDone" ) ; + m_id = id ; + FileStore & file_store = dynamic_cast(m_store) ; + m_scanner_client->startScanning( file_store.contentPath(id) ) ; +} + +void GSmtp::ProtocolMessageScanner::scannerDone( bool /* reason_is_from_scanner */ , std::string reason ) +{ + const bool ok = reason.empty() ; + ProtocolMessageForward::processDone( ok , m_id , reason ) ; +} + +void GSmtp::ProtocolMessageScanner::clear() +{ + scannerInit() ; + Base::clear() ; +} + diff --git a/src/gsmtp/gprotocolmessagescanner.h b/src/gsmtp/gprotocolmessagescanner.h new file mode 100644 index 0000000..f1b8b8d --- /dev/null +++ b/src/gsmtp/gprotocolmessagescanner.h @@ -0,0 +1,94 @@ +// +// Copyright (C) 2001-2003 Graeme Walker +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// === +// +// gprotocolmessagescanner.h +// + +#ifndef G_SMTP_PROTOCOL_MESSAGE_SCANNER_H +#define G_SMTP_PROTOCOL_MESSAGE_SCANNER_H + +#include "gdef.h" +#include "gsmtp.h" +#include "gprotocolmessage.h" +#include "gprotocolmessagestore.h" +#include "gprotocolmessageforward.h" +#include "gscannerclient.h" +#include "gsecrets.h" +#include "gsmtpclient.h" +#include "gmessagestore.h" +#include "gnewmessage.h" +#include +#include + +namespace GSmtp +{ + class ProtocolMessageScanner ; +} + +// Class: GSmtp::ProtocolMessageScanner +// Description: A derivation of ProtocolMessageForward which adds in +// a scanning step. +// +// The scanning part deletages to a ScannerClient data member. +// +// See also: ProtocolMessageStore, ProtocolMessageForward +// +class GSmtp::ProtocolMessageScanner : public GSmtp::ProtocolMessageForward +{ +public: + ProtocolMessageScanner( MessageStore & store , const Secrets & client_secrets , + const std::string & smtp_server_address , + unsigned int smtp_response_timeout , unsigned int smtp_connection_timeout , + const std::string & scanner_server_address , + unsigned int scanner_response_timeout , unsigned int scanner_connection_timeout ) ; + // Constructor. The 'store' and 'client-secrets' references + // are kept. + + virtual ~ProtocolMessageScanner() ; + // Destructor. + + virtual G::Signal3 & preparedSignal() ; + // See ProtocolMessage. + + virtual bool prepare() ; + // See ProtocolMessage. + + virtual void clear() ; + // See ProtocolMessage. + +private: + void operator=( const ProtocolMessageScanner & ) ; // not implemented + void connectDone( std::string reason , bool ) ; + void storageDone( bool success , unsigned long id , std::string reason ) ; + void scannerDone( bool b , std::string reason ) ; + void scannerInit() ; + +private: + typedef ProtocolMessageForward Base ; + MessageStore & m_store ; + std::string m_scanner_server ; + unsigned int m_scanner_response_timeout ; + unsigned int m_scanner_connection_timeout ; + std::auto_ptr m_scanner_client ; + G::Signal3 m_prepared_signal ; + unsigned long m_id ; +} ; + +#endif diff --git a/src/gsmtp/gprotocolmessagestore.cpp b/src/gsmtp/gprotocolmessagestore.cpp index 13f3e48..e9e9e3e 100644 --- a/src/gsmtp/gprotocolmessagestore.cpp +++ b/src/gsmtp/gprotocolmessagestore.cpp @@ -68,6 +68,11 @@ bool GSmtp::ProtocolMessageStore::setFrom( const std::string & from ) } } +bool GSmtp::ProtocolMessageStore::prepare() +{ + return false ; // no async preparation required +} + bool GSmtp::ProtocolMessageStore::addTo( const std::string & to , Verifier::Status to_status ) { G_ASSERT( m_msg.get() != NULL ) ; @@ -121,19 +126,23 @@ void GSmtp::ProtocolMessageStore::process( const std::string & auth_id , const s id = m_msg->id() ; } clear() ; - m_signal.emit( true , id , std::string() ) ; + m_done_signal.emit( true , id , std::string() ) ; } catch( std::exception & e ) { G_ERROR( "GSmtp::ProtocolMessage::process: error: " << e.what() ) ; clear() ; - m_signal.emit( false , 0UL , e.what() ) ; + m_done_signal.emit( false , 0UL , e.what() ) ; } } G::Signal3 & GSmtp::ProtocolMessageStore::doneSignal() { - return m_signal ; + return m_done_signal ; } +G::Signal3 & GSmtp::ProtocolMessageStore::preparedSignal() +{ + return m_prepared_signal ; +} diff --git a/src/gsmtp/gprotocolmessagestore.h b/src/gsmtp/gprotocolmessagestore.h index e372724..698a45a 100644 --- a/src/gsmtp/gprotocolmessagestore.h +++ b/src/gsmtp/gprotocolmessagestore.h @@ -56,12 +56,18 @@ public: virtual G::Signal3 & doneSignal() ; // See ProtocolMessage. + virtual G::Signal3 & preparedSignal() ; + // See ProtocolMessage. + virtual void clear() ; // See ProtocolMessage. virtual bool setFrom( const std::string & from_user ) ; // See ProtocolMessage. + virtual bool prepare() ; + // See ProtocolMessage. + virtual bool addTo( const std::string & to_user , Verifier::Status to_status ) ; // See ProtocolMessage. @@ -84,7 +90,8 @@ private: MessageStore & m_store ; std::auto_ptr m_msg ; std::string m_from ; - G::Signal3 m_signal ; + G::Signal3 m_done_signal ; + G::Signal3 m_prepared_signal ; } ; #endif diff --git a/src/gsmtp/gscannerclient.cpp b/src/gsmtp/gscannerclient.cpp new file mode 100644 index 0000000..f05ab9a --- /dev/null +++ b/src/gsmtp/gscannerclient.cpp @@ -0,0 +1,255 @@ +// +// Copyright (C) 2001-2003 Graeme Walker +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// === +// +// gscannerclient.cpp +// + +#include "gdef.h" +#include "gnet.h" +#include "gsmtp.h" +#include "gstr.h" +#include "gscannerclient.h" +#include "gassert.h" + +GSmtp::ScannerClient::ScannerClient( const std::string & host_and_service , + unsigned int connect_timeout , unsigned int response_timeout ) : + m_timer(*this) , + m_connect_timeout(connect_timeout) , + m_response_timeout(response_timeout) , + m_state("idle") , + m_socket(NULL) , + m_host(hostPart(host_and_service)) , + m_service(servicePart(host_and_service)) +{ + G_DEBUG( "GSmtp::ScannerClient::ctor: " << host_and_service ) ; +} + +GSmtp::ScannerClient::ScannerClient( const std::string & host , const std::string & service , + unsigned int connect_timeout , unsigned int response_timeout ) : + m_timer(*this) , + m_connect_timeout(connect_timeout) , + m_response_timeout(response_timeout) , + m_state("idle") , + m_socket(NULL) , + m_host(host) , + m_service(service) +{ + G_DEBUG( "GSmtp::ScannerClient::ctor: " << host << ":" << service ) ; +} + +G::Signal2 & GSmtp::ScannerClient::connectedSignal() +{ + return m_connected_signal ; +} + +G::Signal2 & GSmtp::ScannerClient::doneSignal() +{ + return m_done_signal ; +} + +void GSmtp::ScannerClient::startConnecting() +{ + G_DEBUG( "GSmtp::ScannerClient::startConnecting" ) ; + G_ASSERT( m_state == "idle" ) ; + + m_timer.startTimer( m_connect_timeout ) ; + setState( "connecting" ) ; + if( ! connect( m_host , m_service ) ) + { + setState( "failing" ) ; + m_timer.cancelTimer() ; + m_timer.startTimer(0U) ; + } +} + +void GSmtp::ScannerClient::onConnect( GNet::Socket & socket ) +{ + G_DEBUG( "GSmtp::ScannerClient::onConnect" ) ; + G_ASSERT( m_state == "connecting" ) ; + + m_socket = &socket ; + setState( "temp" ) ; + m_timer.cancelTimer() ; + m_timer.startTimer(0U) ; +} + +void GSmtp::ScannerClient::onError( const std::string & error ) +{ + G_WARNING( "GSmtp::ScannerClient::onError: connect error: " << error ) ; + G_ASSERT( m_state == "connecting" ) ; + + m_timer.cancelTimer() ; + setState( "end" ) ; + m_connected_signal.emit( error , GNet::Client::canRetry(error) ) ; +} + +std::string GSmtp::ScannerClient::startScanning( const G::Path & path ) +{ + G_DEBUG( "GSmtp::ScannerClient::startScanning: \"" << path << "\"" ) ; + G_ASSERT( m_state == "connected" || m_state == "disconnected" ) ; + + if( m_state == "disconnected" ) + { + setState( "end" ) ; + return "disconnected" ; + } + else + { + m_timer.startTimer( m_response_timeout ) ; + std::string data = request( path ) ; + ssize_t rc = m_socket->write( data.c_str() , data.length() ) ; + + std::string result = + rc < static_cast(data.length()) ? + ( m_socket->eWouldBlock() ? + std::string("flow control asserted by peer") : + std::string("connection lost") ) : + std::string() ; + + bool ok = result.empty() ; + if( ok ) + { + setState( "scanning" ) ; + } + else + { + setState( "end" ) ; + m_timer.cancelTimer() ; + } + return result ; + } +} + +void GSmtp::ScannerClient::onDisconnect() +{ + G_DEBUG( "GSmtp::ScannerClient::onDisconnect" ) ; + G_ASSERT( m_state == "connected" || m_state == "scanning" ) ; + + if( m_state == "connected" ) + { + setState( "disconnected" ) ; + } + else + { + setState( "end" ) ; + m_done_signal.emit( false , "disconnected" ) ; + } +} + +void GSmtp::ScannerClient::onData( const char * data , size_t size ) +{ + std::string s( data , size ) ; + G_DEBUG( "GSmtp::ScannerClient::onData: " << G::Str::toPrintableAscii(s) ) ; + G_ASSERT( m_state == "scanning" ) ; + + m_line_buffer.add( s ) ; + if( isDone() ) + { + G_DEBUG( "GSmtp::ScannerClient::onData: done" ) ; + m_timer.cancelTimer() ; + m_socket->close() ; + setState( "end" ) ; + bool from_scanner = true ; + m_done_signal.emit( from_scanner , result() ) ; + } +} + +void GSmtp::ScannerClient::onWriteable() +{ + // never gets here + G_DEBUG( "GSmtp::ScannerClient::onWriteable" ) ; +} + +void GSmtp::ScannerClient::onTimeout( GNet::Timer & ) +{ + if( m_state == "failing" ) + { + setState( "end" ) ; + m_connected_signal.emit( "cannot connect" , false ) ; + } + else if( m_state == "temp" ) + { + setState( "connected" ) ; + m_connected_signal.emit( std::string() , false ) ; + } + else if( m_state == "connecting" ) + { + setState( "end" ) ; + m_connected_signal.emit( "connect timeout" , true ) ; + } + else if( m_state == "scanning" ) + { + setState( "end" ) ; + bool from_scanner = false ; + m_done_signal.emit( from_scanner , "response timeout" ) ; + } +} + +void GSmtp::ScannerClient::setState( const std::string & new_state ) +{ + G_DEBUG( "GSmtp::ScannerClient::setState: \"" << m_state << "\" -> \"" << new_state << "\"" ) ; + m_state = new_state ; +} + +//static +std::string GSmtp::ScannerClient::hostPart( const std::string & s ) +{ + size_t pos = s.find(":") ; + if( pos == std::string::npos ) + { + throw FormatError(s) ; + } + return s.substr( 0U , pos ) ; +} + +//static +std::string GSmtp::ScannerClient::servicePart( const std::string & s ) +{ + size_t pos = s.find(":") ; + if( pos == std::string::npos || (pos+1U) == s.length() ) + { + throw FormatError(s) ; + } + return s.substr( pos+1U ) ; +} + +// scanner customisation... + +std::string GSmtp::ScannerClient::request( const G::Path & path ) const +{ + std::string prefix = "AREA: " ; + return prefix + path.str() + "\n" ; +} + +bool GSmtp::ScannerClient::isDone() const +{ + return m_line_buffer.more() ; +} + +std::string GSmtp::ScannerClient::result() +{ + std::string s = m_line_buffer.line() ; + if( s.find("ok") != std::string::npos ) + return std::string() ; + else + return s ; +} + + diff --git a/src/gsmtp/gscannerclient.h b/src/gsmtp/gscannerclient.h new file mode 100644 index 0000000..e2f25ab --- /dev/null +++ b/src/gsmtp/gscannerclient.h @@ -0,0 +1,124 @@ +// +// Copyright (C) 2001-2003 Graeme Walker +// +// 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., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// === +// +// gscannerclient.h +// + +#ifndef G_SCANNER_CLIENT_H +#define G_SCANNER_CLIENT_H + +#include "gdef.h" +#include "gnet.h" +#include "gsmtp.h" +#include "gclient.h" +#include "gpath.h" +#include "gslot.h" +#include "gtimer.h" +#include "glinebuffer.h" +#include "gexception.h" + +namespace GSmtp +{ + class ScannerClient ; +} + +// Class: GSmtp::ScannerClient +// Description: A class which interacts with a remote 'scanner' process. The +// interface is asynchronous, with separate 'connect' and 'scan' stages. +// +class GSmtp::ScannerClient : private GNet::Client , private GNet::TimeoutHandler +{ +public: + G_EXCEPTION( FormatError , "scanner server format error" ) ; + + ScannerClient( const std::string & host_and_service , + unsigned int connect_timeout , unsigned int response_timeout ) ; + // Constructor. + + ScannerClient( const std::string & host , const std::string & service , + unsigned int connect_timeout , unsigned int response_timeout ) ; + // Constructor. + + G::Signal2 & connectedSignal() ; + // Returns a signal which indicates that connection + // is complete. + // + // The signal parameters are the empty string on success + // or a failure reason, and a boolean flag which is + // true if the failure reason implies a temporary + // error. + + G::Signal2 & doneSignal() ; + // Returns a signal which indicates that scanning + // is complete. + // + // The signal parameters are a boolean flag and + // a string. If the flag is true then the string is + // the response from the scanner, empty on success. + // If the flag is false then there has been a network + // error and the string is a reason string. + + void startConnecting() ; + // Initiates a connection to the scanner. + // + // The connectedSignal() will get raised + // some time later. + + std::string startScanning( const G::Path & path ) ; + // Starts the scanning process for the given + // content file. + // + // Returns an error string if an immediate error. + // + // The doneSignal() will get raised some time + // after startScanning() returns the empty + // string. + +private: + virtual void onConnect( GNet::Socket & socket ) ; // GNet::Client + virtual void onDisconnect() ; // GNet::Client + virtual void onData( const char * data , size_t size ) ; // GNet::Client + virtual void onWriteable() ; // GNet::Client + virtual void onError( const std::string & error ) ; // GNet::Client + virtual void onTimeout( GNet::Timer & ) ; // GNet::TimeoutHandler + void raiseSignal( const std::string & ) ; + std::string request( const G::Path & ) const ; + bool isDone() const ; + std::string result() ; + void setState( const std::string & ) ; + static std::string hostPart( const std::string & ) ; + static std::string servicePart( const std::string & ) ; + ScannerClient( const ScannerClient & ) ; // not implemented + void operator=( const ScannerClient & ) ; // not implemented + +private: + G::Signal2 m_done_signal ; + G::Signal2 m_connected_signal ; + GNet::Timer m_timer ; + unsigned int m_connect_timeout ; + unsigned int m_response_timeout ; + std::string m_state ; + GNet::Socket * m_socket ; + GNet::LineBuffer m_line_buffer ; + std::string m_host ; + std::string m_service ; +} ; + +#endif diff --git a/src/gsmtp/gserverprotocol.cpp b/src/gsmtp/gserverprotocol.cpp index f890dfe..a9f17bc 100644 --- a/src/gsmtp/gserverprotocol.cpp +++ b/src/gsmtp/gserverprotocol.cpp @@ -45,6 +45,7 @@ GSmtp::ServerProtocol::ServerProtocol( Sender & sender , Verifier & verifier , P m_sasl(secrets) { m_pmessage.doneSignal().connect( G::slot(*this,&ServerProtocol::processDone) ) ; + m_pmessage.preparedSignal().connect( G::slot(*this,&ServerProtocol::prepareDone) ) ; // (dont send anything to the peer from this ctor -- the Sender // object is not fuly constructed) @@ -56,7 +57,8 @@ GSmtp::ServerProtocol::ServerProtocol( Sender & sender , Verifier & verifier , P m_fsm.addTransition( eVrfy , s_Any , s_Same , &GSmtp::ServerProtocol::doVrfy ) ; m_fsm.addTransition( eEhlo , s_Any , sIdle , &GSmtp::ServerProtocol::doEhlo , s_Same ) ; m_fsm.addTransition( eHelo , s_Any , sIdle , &GSmtp::ServerProtocol::doHelo , s_Same ) ; - m_fsm.addTransition( eMail , sIdle , sGotMail , &GSmtp::ServerProtocol::doMail , sIdle ) ; + m_fsm.addTransition( eMail , sIdle , sPrepare , &GSmtp::ServerProtocol::doMailPrepare , sIdle ) ; + m_fsm.addTransition( ePrepared, sPrepare, sGotMail , &GSmtp::ServerProtocol::doMail , sIdle ) ; m_fsm.addTransition( eRcpt , sGotMail, sGotRcpt , &GSmtp::ServerProtocol::doRcpt , sGotMail ) ; m_fsm.addTransition( eRcpt , sGotRcpt, sGotRcpt , &GSmtp::ServerProtocol::doRcpt ) ; m_fsm.addTransition( eData , sGotMail, sIdle , &GSmtp::ServerProtocol::doNoRecipients ) ; @@ -128,6 +130,7 @@ bool GSmtp::ServerProtocol::apply( const std::string & line ) void GSmtp::ServerProtocol::processDone( bool success , unsigned long , std::string reason ) { + G_DEBUG( "GSmtp::ServerProtocol::processDone: " << success << ", \"" << reason << "\"" ) ; G_ASSERT( m_fsm.state() == sProcessing ) ; // (a RSET will call m_pmessage.clear() to cancel the callback) if( m_fsm.state() == sProcessing ) // just in case { @@ -136,12 +139,11 @@ void GSmtp::ServerProtocol::processDone( bool success , unsigned long , std::str } } - void GSmtp::ServerProtocol::doQuit( const std::string & , bool & ) { sendClosing() ; m_sender.protocolDone() ; - // do nothing more -- this object may have been deleted already + // do nothing more -- this object may have been deleted in protocolDone() } void GSmtp::ServerProtocol::doNoop( const std::string & , bool & ) @@ -314,7 +316,7 @@ void GSmtp::ServerProtocol::sendChallenge( const std::string & s ) send( std::string("334 ") + Base64::encode(s,std::string()) ) ; } -void GSmtp::ServerProtocol::doMail( const std::string & line , bool & predicate ) +void GSmtp::ServerProtocol::doMailPrepare( const std::string & line , bool & predicate ) { if( m_sasl.active() && !m_sasl.trusted(m_peer_address) && !m_authenticated ) { @@ -328,21 +330,56 @@ void GSmtp::ServerProtocol::doMail( const std::string & line , bool & predicate bool ok = m_pmessage.setFrom( from ) ; predicate = ok ; if( ok ) - sendMailReply() ; + { + bool async_prepare = m_pmessage.prepare() ; + if( ! async_prepare ) + m_fsm.apply( *this , ePrepared , "" ) ; // re-entrancy ok + } else + { sendBadFrom( from ) ; + } + } +} + +void GSmtp::ServerProtocol::prepareDone( bool success , bool temporary_fault , std::string reason ) +{ + G_DEBUG( "GSmtp::ServerProtocol::prepareDone: " << success << ", " + << temporary_fault << ", \"" << reason << "\"" ) ; + + // as a kludge mark temporary failures by prepending a space + if( !success && temporary_fault ) + reason = std::string(" ")+reason ; + + m_fsm.apply( *this , ePrepared , reason ) ; +} + +void GSmtp::ServerProtocol::doMail( const std::string & line , bool & predicate ) +{ + // here 'line' comes from prepareDone(), or empty if no preparation stage + G_DEBUG( "GSmtp::ServerProtocol::doMail: \"" << line << "\"" ) ; + if( line.empty() ) + { + sendMailReply() ; + } + else + { + predicate = false ; + bool temporary = line.at(0U) == ' ' ; + sendMailError( line.substr(temporary?1U:0U) , temporary ) ; } } void GSmtp::ServerProtocol::doRcpt( const std::string & line , bool & predicate ) { std::string to = parseTo( line ) ; - bool ok = m_pmessage.addTo( to , verify(to,m_pmessage.from()) ) ; + Verifier::Status status = verify( to , m_pmessage.from() ) ; + bool ok = m_pmessage.addTo( to , status ) ; predicate = ok ; if( ok ) sendRcptReply() ; else - sendBadTo( to ) ; + sendBadTo( G::Str::toPrintableAscii(status.reason) ) ; } void GSmtp::ServerProtocol::doUnknown( const std::string & line , bool & ) @@ -473,6 +510,12 @@ void GSmtp::ServerProtocol::sendMailReply() sendOk() ; } +void GSmtp::ServerProtocol::sendMailError( const std::string & reason , bool temporary ) +{ + std::string number( temporary ? "452" : "550" ) ; + send( number + " " + reason ) ; +} + void GSmtp::ServerProtocol::sendCompletionReply( bool ok , const std::string & reason ) { if( ok ) @@ -491,9 +534,9 @@ void GSmtp::ServerProtocol::sendBadFrom( const std::string & /*from*/ ) send( "553 mailbox name not allowed" ) ; } -void GSmtp::ServerProtocol::sendBadTo( const std::string & to ) +void GSmtp::ServerProtocol::sendBadTo( const std::string & text ) { - send( std::string("550 mailbox unavailable: ") + to ) ; + send( std::string("550 mailbox unavailable: ") + text ) ; } void GSmtp::ServerProtocol::sendEhloReply( const std::string & domain ) diff --git a/src/gsmtp/gserverprotocol.h b/src/gsmtp/gserverprotocol.h index 1a475fc..a3a885e 100644 --- a/src/gsmtp/gserverprotocol.h +++ b/src/gsmtp/gserverprotocol.h @@ -113,6 +113,7 @@ private: eData , eRcpt , eMail , + ePrepared , eVrfy , eHelp , eAuth , @@ -125,6 +126,7 @@ private: sEnd , sIdle , sGotMail , + sPrepare , sGotRcpt , sData , sProcessing , @@ -143,6 +145,7 @@ private: std::string commandLine( const std::string & line ) const ; static std::string crlf() ; void processDone( bool , unsigned long , std::string ) ; // ProtocolMessage::doneSignal() + void prepareDone( bool , bool , std::string ) ; bool isEndOfText( const std::string & ) const ; bool isEscaped( const std::string & ) const ; void doNoop( const std::string & , bool & ) ; @@ -151,6 +154,7 @@ private: void doHelo( const std::string & , bool & ) ; void doAuth( const std::string & , bool & ) ; void doAuthData( const std::string & , bool & ) ; + void doMailPrepare( const std::string & line , bool & ) ; void doMail( const std::string & line , bool & ) ; void doRcpt( const std::string & line , bool & ) ; void doUnknown( const std::string & line , bool & ) ; @@ -169,6 +173,7 @@ private: void sendEhloReply( const std::string & ) ; void sendRsetReply() ; void sendMailReply() ; + void sendMailError( const std::string & , bool ) ; void sendRcptReply() ; void sendDataReply() ; void sendCompletionReply( bool ok , const std::string & ) ; diff --git a/src/gsmtp/gsmtpserver.cpp b/src/gsmtp/gsmtpserver.cpp index d1b38bc..f3ea883 100644 --- a/src/gsmtp/gsmtpserver.cpp +++ b/src/gsmtp/gsmtpserver.cpp @@ -26,6 +26,7 @@ #include "gsmtpserver.h" #include "gprotocolmessagestore.h" #include "gprotocolmessageforward.h" +#include "gprotocolmessagescanner.h" #include "gmemory.h" #include "glocal.h" #include "glog.h" @@ -116,17 +117,22 @@ GSmtp::Server::Server( MessageStore & store , const Secrets & server_secrets , const Verifier & verifier , const std::string & ident , bool allow_remote , unsigned int port , const AddressList & interfaces , - const std::string & downstream_server , - unsigned int response_timeout , unsigned int connection_timeout , - const Secrets & client_secrets ) : + const std::string & smtp_server , + unsigned int smtp_response_timeout , unsigned int smtp_connection_timeout , + const Secrets & client_secrets , + const std::string & scanner_server , + unsigned int scanner_response_timeout , unsigned int scanner_connection_timeout ) : m_store(store) , m_ident(ident) , m_allow_remote( allow_remote ) , m_server_secrets(server_secrets) , m_verifier(verifier) , - m_downstream_server(downstream_server) , - m_response_timeout(response_timeout) , - m_connection_timeout(connection_timeout) , + m_smtp_server(smtp_server) , + m_smtp_response_timeout(smtp_response_timeout) , + m_smtp_connection_timeout(smtp_connection_timeout) , + m_scanner_server(scanner_server) , + m_scanner_response_timeout(scanner_response_timeout) , + m_scanner_connection_timeout(scanner_connection_timeout) , m_client_secrets(client_secrets) , m_gnet_server_1( *this ) , m_gnet_server_2( *this ) , @@ -183,17 +189,34 @@ GNet::ServerPeer * GSmtp::Server::newPeer( GNet::Server::PeerInfo peer_info ) return NULL ; } - const bool immediate = ! m_downstream_server.empty() ; - - std::auto_ptr pmessage( - immediate ? - static_cast(new ProtocolMessageForward(m_store, - m_client_secrets,m_downstream_server,m_response_timeout,m_connection_timeout)) : - static_cast(new ProtocolMessageStore(m_store)) ) ; - + std::auto_ptr pmessage( newProtocolMessage() ) ; return new ServerPeer( peer_info , *this , pmessage , m_ident , m_server_secrets , m_verifier ) ; } +GSmtp::ProtocolMessage * GSmtp::Server::newProtocolMessage() +{ + const bool immediate = ! m_smtp_server.empty() ; + const bool scan = ! m_scanner_server.empty() ; + if( immediate && scan ) + { + G_DEBUG( "GSmtp::Server::newProtocolMessage: new ProtocolMessageScanner" ) ; + return new ProtocolMessageScanner(m_store,m_client_secrets, + m_smtp_server,m_smtp_response_timeout,m_smtp_connection_timeout, + m_scanner_server,m_scanner_response_timeout,m_scanner_connection_timeout) ; + } + else if( immediate ) + { + G_DEBUG( "GSmtp::Server::newProtocolMessage: new ProtocolMessageForward" ) ; + return new ProtocolMessageForward(m_store,m_client_secrets, + m_smtp_server,m_smtp_response_timeout,m_smtp_connection_timeout) ; + } + else + { + G_DEBUG( "GSmtp::Server::newProtocolMessage: new ProtocolMessageStore" ) ; + return new ProtocolMessageStore(m_store) ; + } +} + // === GSmtp::ServerImp::ServerImp( GSmtp::Server & server ) : diff --git a/src/gsmtp/gsmtpserver.h b/src/gsmtp/gsmtpserver.h index 5c374a9..2ad2c26 100644 --- a/src/gsmtp/gsmtpserver.h +++ b/src/gsmtp/gsmtpserver.h @@ -111,10 +111,13 @@ public: const Secrets & server_secrets , const Verifier & verifier , const std::string & ident , bool allow_remote , unsigned int port , const AddressList & interfaces , - const std::string & downstream_server_address , - unsigned int response_timeout , - unsigned int connection_timeout , - const Secrets & client_secrets ) ; + const std::string & smtp_server_address , + unsigned int smtp_response_timeout , + unsigned int smtp_connection_timeout , + const Secrets & client_secrets , + const std::string & scanner_address , + unsigned int scanner_response_timeout , + unsigned int scanner_connection_timeout ) ; // Constructor. Listens on the given port number // using INET_ANY if 'interfaces' is empty, or // on specific interfaces otherwise. Currently @@ -138,6 +141,7 @@ public: private: void bind( ServerImp & , GNet::Address , unsigned int ) ; + ProtocolMessage * newProtocolMessage() ; ServerImp & imp( size_t n ) ; private: @@ -146,9 +150,12 @@ private: bool m_allow_remote ; const Secrets & m_server_secrets ; Verifier m_verifier ; - std::string m_downstream_server ; - unsigned int m_response_timeout ; - unsigned int m_connection_timeout ; + std::string m_smtp_server ; + unsigned int m_smtp_response_timeout ; + unsigned int m_smtp_connection_timeout ; + std::string m_scanner_server ; + unsigned int m_scanner_response_timeout ; + unsigned int m_scanner_connection_timeout ; const Secrets & m_client_secrets ; ServerImp m_gnet_server_1 ; ServerImp m_gnet_server_2 ; diff --git a/src/gsmtp/gverifier.cpp b/src/gsmtp/gverifier.cpp index 3152ecb..a4cff58 100644 --- a/src/gsmtp/gverifier.cpp +++ b/src/gsmtp/gverifier.cpp @@ -120,8 +120,10 @@ GSmtp::Verifier::Status GSmtp::Verifier::verifyExternal( const std::string & add std::string response ; int rc = G::Process::spawn( G::Root::nobody() , m_path , args , &response ) ; - G::Str::trim( response , "\r\n\t" ) ; G_LOG( "GSmtp::Verifier: " << rc << ": \"" << G::Str::toPrintableAscii(response) << "\"" ) ; + G::Str::trimRight( response , " \n\t" ) ; + G::Str::replaceAll( response , "\r\n" , "\n" ) ; + G::Str::replaceAll( response , "\r" , "" ) ; G::Strings response_parts ; G::Str::splitIntoFields( response , response_parts , "\n" ) ; diff --git a/src/main/commandline.cpp b/src/main/commandline.cpp index 48e8685..2f8b8b0 100644 --- a/src/main/commandline.cpp +++ b/src/main/commandline.cpp @@ -76,6 +76,7 @@ std::string Main::CommandLine::switchSpec( bool is_windows ) << "P!postmaster!deliver to postmaster and reject all other local mailbox addresses!0!!3|" << "Z!verifier!defines an external address verifier program!1!program!3|" << "Q!admin-terminate!!0!!0|" + << "R!scanner!!1!host:port!0|" ; return ss.str() ; } @@ -193,6 +194,11 @@ std::string Main::CommandLine::semanticError() const return "the --forward, --immediate and --poll switches require --forward-to" ; } + if( m_getopt.contains("scanner") && ! ( m_getopt.contains("as-proxy") || m_getopt.contains("immediate") ) ) + { + return "the --scanner switch requires --as-proxy or --immediate" ; + } + const bool log = m_getopt.contains("log") || m_getopt.contains("as-server") || diff --git a/src/main/common.dsp b/src/main/common.dsp index 9d14024..356ad64 100644 --- a/src/main/common.dsp +++ b/src/main/common.dsp @@ -289,6 +289,10 @@ SOURCE=..\gsmtp\gprotocolmessageforward.cpp # End Source File # Begin Source File +SOURCE=..\gsmtp\gprotocolmessagescanner.cpp +# End Source File +# Begin Source File + SOURCE=..\gsmtp\gprotocolmessagestore.cpp # End Source File # Begin Source File @@ -329,6 +333,10 @@ SOURCE=..\gsmtp\gsasl_native.cpp # End Source File # Begin Source File +SOURCE=..\gsmtp\gscannerclient.cpp +# End Source File +# Begin Source File + SOURCE=..\win32\gscmap.cpp # End Source File # Begin Source File diff --git a/src/main/configuration.cpp b/src/main/configuration.cpp index 0b1c1bd..992fdbc 100644 --- a/src/main/configuration.cpp +++ b/src/main/configuration.cpp @@ -67,7 +67,7 @@ std::string Main::Configuration::str( const std::string & p , const std::string << p << "spool directory: " << spoolDir() << eol << p << "immediate forwarding? " << yn(immediate()) << eol << p << "mail processor: " << (useFilter()?filter():na()) << eol - //<< p << "address verifier: " << na(verifier().str()) << eol + << p << "address verifier: " << na(verifier().str()) << eol << p << "admin port: " << (doAdmin()?G::Str::fromUInt(adminPort()):na()) << eol << p << "run as daemon? " << yn(daemon()) << eol << p << "verbose logging? " << yn(verbose()) << eol @@ -299,4 +299,18 @@ bool Main::Configuration::withTerminate() const return m_cl.contains("admin-terminate") ; } +std::string Main::Configuration::scannerAddress() const +{ + return m_cl.contains("scanner") ? m_cl.value("scanner") : std::string() ; +} + +unsigned int Main::Configuration::scannerConnectionTimeout() const +{ + return 10U ; // for now +} + +unsigned int Main::Configuration::scannerResponseTimeout() const +{ + return 90U ; // for now +} diff --git a/src/main/configuration.h b/src/main/configuration.h index e193a96..29edc64 100644 --- a/src/main/configuration.h +++ b/src/main/configuration.h @@ -167,6 +167,15 @@ public: // Returns true if the admin interface should support the // terminate command. + std::string scannerAddress() const ; + // Returns the address of a scanner process. + + unsigned int scannerConnectionTimeout() const ; + // Returns a timeout for connecting to the scanner process. + + unsigned int scannerResponseTimeout() const ; + // Returns a timeout for talking to the scanner process. + private: const CommandLine & m_cl ; diff --git a/src/main/doxygen.cfg b/src/main/doxygen.cfg old mode 100755 new mode 100644 index 2c6a3ab..8543731 --- a/src/main/doxygen.cfg +++ b/src/main/doxygen.cfg @@ -3,7 +3,7 @@ # General configuration options #--------------------------------------------------------------------------- PROJECT_NAME = E-MailRelay -PROJECT_NUMBER = 1.1.1 +PROJECT_NUMBER = 1.1.2 OUTPUT_DIRECTORY = OUTPUT_LANGUAGE = English EXTRACT_ALL = YES diff --git a/src/main/doxygen.h b/src/main/doxygen.h index 0111cd6..7080b70 100644 --- a/src/main/doxygen.h +++ b/src/main/doxygen.h @@ -27,10 +27,19 @@ source code. The Namespace List is a good starting for browsing -- the detailed description section towards the end of each namespace page gives a list of the namespace's key classes. -The E-MailRelay developer's guide also gives an overview -of the code structure, including simple class diagrams for the -GNet and -GSmtp namespaces. +The E-MailRelay design and implementation guide gives an overview +of the code structure, and there are a number of supporting diagrams: + */ diff --git a/src/main/run.cpp b/src/main/run.cpp old mode 100755 new mode 100644 index 7b62f67..29bae16 --- a/src/main/run.cpp +++ b/src/main/run.cpp @@ -50,7 +50,7 @@ //static std::string Main::Run::versionNumber() { - return "1.1.1" ; + return "1.1.2" ; } Main::Run::Run( Main::Output & output , const G::Arg & arg , const std::string & switch_spec ) : @@ -147,21 +147,41 @@ void Main::Run::run() } } +void Main::Run::checkPort( const std::string & interface_ , unsigned int port ) +{ + GNet::Address address = + interface_.length() ? + GNet::Address(interface_,port) : + GNet::Address(port) ; + GNet::Server::canBind( address , true ) ; +} + +void Main::Run::checkPorts() const +{ + if( cfg().doServing() && cfg().doSmtp() ) + checkPort( cfg().interface_() , cfg().port() ) ; + + if( cfg().doServing() && cfg().doAdmin() ) + checkPort( cfg().interface_() , cfg().adminPort() ) ; +} + void Main::Run::runCore() { // fqdn override option // GNet::Local::fqdn( cfg().fqdn() ) ; - // daemonising + // tighten the umask // - G::PidFile pid_file ; G::Process::Umask::set( G::Process::Umask::Tightest ) ; - if( cfg().daemon() ) closeFiles() ; // before opening any sockets or message-store streams - if( cfg().usePidFile() ) pid_file.init( G::Path(cfg().pidFile()) ) ; - if( cfg().daemon() ) G::Daemon::detach( pid_file ) ; - // release root privileges + // close inherited file descriptors to avoid locking file + // systems when running as a daemon -- this has to be done + // early, before opening any sockets or message-store streams + // + if( cfg().daemon() ) closeFiles() ; + + // release root privileges and extra group memberships // G::Root::init( cfg().nobody() ) ; @@ -172,6 +192,10 @@ void Main::Run::runCore() if( ! event_loop->init() ) throw G::Exception( "cannot initialise network layer" ) ; + // early check on socket bindability + // + checkPorts() ; + // network monitor singleton // GNet::Monitor monitor ; @@ -189,6 +213,12 @@ void Main::Run::runCore() m_client_secrets <<= new GSmtp::Secrets( cfg().clientSecretsFile() , "client" ) ; GSmtp::Secrets server_secrets( cfg().serverSecretsFile() , "server" ) ; + // daemonise + // + G::PidFile pid_file ; + if( cfg().usePidFile() ) pid_file.init( G::Path(cfg().pidFile()) ) ; + if( cfg().daemon() ) G::Daemon::detach( pid_file ) ; + // run as forwarding agent // if( cfg().doForwarding() ) @@ -228,7 +258,10 @@ void Main::Run::doServing( GSmtp::MessageStore & store , const GSmtp::Secrets & cfg().immediate() ? cfg().serverAddress() : std::string() , cfg().responseTimeout() , cfg().connectionTimeout() , - client_secrets ) ; + client_secrets , + cfg().scannerAddress() , + cfg().scannerResponseTimeout() , + cfg().scannerConnectionTimeout() ) ; } if( cfg().doAdmin() ) diff --git a/src/main/run.h b/src/main/run.h index 34f813d..d7a9991 100644 --- a/src/main/run.h +++ b/src/main/run.h @@ -108,6 +108,8 @@ private: void raiseNetworkEvent( std::string , std::string ) ; void emit( const std::string & , const std::string & , const std::string & ) ; std::string doPoll() ; + void checkPorts() const ; + static void checkPort( const std::string & , unsigned int ) ; private: Output & m_output ;