This commit is contained in:
Graeme Walker 2002-03-26 12:00:00 +00:00
parent 4905d3db56
commit a21313453c
20 changed files with 133 additions and 94 deletions

View File

@ -1,6 +1,12 @@
E-MailRelay Change Log E-MailRelay Change Log
====================== ======================
0.9.7 -> 0.9.8
--------------
* Fix for running preprocessor ("--filter") as root.
* Ignore bogus "AUTH=LOGIN" lines in EHLO response.
* Submit utility improved to work with mutt.
0.9.6 -> 0.9.7 0.9.6 -> 0.9.7
-------------- --------------
* CRAM-MD5 authentication mechanism added. * CRAM-MD5 authentication mechanism added.

2
configure vendored
View File

@ -1124,7 +1124,7 @@ fi
PACKAGE=emailrelay PACKAGE=emailrelay
VERSION=0.9.7 VERSION=0.9.8
if test "`cd $srcdir && pwd`" != "`pwd`" && test -f $srcdir/config.status; then if test "`cd $srcdir && pwd`" != "`pwd`" && test -f $srcdir/config.status; then
{ { echo "$as_me:1130: error: source directory already configured; run \"make distclean\" there first" >&5 { { echo "$as_me:1130: error: source directory already configured; run \"make distclean\" there first" >&5

View File

@ -39,7 +39,7 @@ dnl ===
dnl Process this file with autoconf to produce a configure script. dnl Process this file with autoconf to produce a configure script.
AC_INIT(src/main/gsmtp.h) AC_INIT(src/main/gsmtp.h)
AM_INIT_AUTOMAKE(emailrelay,0.9.7) AM_INIT_AUTOMAKE(emailrelay,0.9.8)
AM_CONFIG_HEADER(config.h) AM_CONFIG_HEADER(config.h)
dnl === dnl ===

1
doc/Makefile.am Normal file → Executable file
View File

@ -63,6 +63,7 @@ install-data-local:
$(INSTALL) $(top_srcdir)/doc/emailrelay.1 $(destdir)$(mandir)/man1/emailrelay.1 $(INSTALL) $(top_srcdir)/doc/emailrelay.1 $(destdir)$(mandir)/man1/emailrelay.1
$(INSTALL) $(top_srcdir)/doc/emailrelay-passwd.1 $(destdir)$(mandir)/man1/emailrelay-passwd.1 $(INSTALL) $(top_srcdir)/doc/emailrelay-passwd.1 $(destdir)$(mandir)/man1/emailrelay-passwd.1
$(INSTALL) $(top_srcdir)/doc/emailrelay-poke.1 $(destdir)$(mandir)/man1/emailrelay-poke.1 $(INSTALL) $(top_srcdir)/doc/emailrelay-poke.1 $(destdir)$(mandir)/man1/emailrelay-poke.1
$(INSTALL) $(top_srcdir)/doc/emailrelay-submit.1 $(destdir)$(mandir)/man1/emailrelay-submit.1
$(mkinstalldirs) $(destdir)$(pkgdatadir)/graphics $(mkinstalldirs) $(destdir)$(pkgdatadir)/graphics
$(INSTALL) $(top_srcdir)/doc/graphics/bullet.gif $(destdir)$(pkgdatadir)/graphics/bullet.gif $(INSTALL) $(top_srcdir)/doc/graphics/bullet.gif $(destdir)$(pkgdatadir)/graphics/bullet.gif
$(mkinstalldirs) $(destdir)$(pkgdatadir)/html $(mkinstalldirs) $(destdir)$(pkgdatadir)/html

View File

@ -264,6 +264,7 @@ install-data-local:
$(INSTALL) $(top_srcdir)/doc/emailrelay.1 $(destdir)$(mandir)/man1/emailrelay.1 $(INSTALL) $(top_srcdir)/doc/emailrelay.1 $(destdir)$(mandir)/man1/emailrelay.1
$(INSTALL) $(top_srcdir)/doc/emailrelay-passwd.1 $(destdir)$(mandir)/man1/emailrelay-passwd.1 $(INSTALL) $(top_srcdir)/doc/emailrelay-passwd.1 $(destdir)$(mandir)/man1/emailrelay-passwd.1
$(INSTALL) $(top_srcdir)/doc/emailrelay-poke.1 $(destdir)$(mandir)/man1/emailrelay-poke.1 $(INSTALL) $(top_srcdir)/doc/emailrelay-poke.1 $(destdir)$(mandir)/man1/emailrelay-poke.1
$(INSTALL) $(top_srcdir)/doc/emailrelay-submit.1 $(destdir)$(mandir)/man1/emailrelay-submit.1
$(mkinstalldirs) $(destdir)$(pkgdatadir)/graphics $(mkinstalldirs) $(destdir)$(pkgdatadir)/graphics
$(INSTALL) $(top_srcdir)/doc/graphics/bullet.gif $(destdir)$(pkgdatadir)/graphics/bullet.gif $(INSTALL) $(top_srcdir)/doc/graphics/bullet.gif $(destdir)$(pkgdatadir)/graphics/bullet.gif
$(mkinstalldirs) $(destdir)$(pkgdatadir)/html $(mkinstalldirs) $(destdir)$(pkgdatadir)/html

View File

@ -23,11 +23,13 @@ emailrelay-submit \- a submission utility for E-MailRelay
.B emailrelay-submit .B emailrelay-submit
[--help] [--from [--help] [--from
.IR from-address ] .IR from-address ]
.I to-address
.RI [ to-address \ ...] .RI [ to-address \ ...]
.SH DESCRIPTION .SH DESCRIPTION
.I emailrelay-submit .I emailrelay-submit
is a utility which reads an RFC822 email message from the standard is a utility which reads an RFC822 email message from the standard
input, and writes it into the input, with SMTP envelope recipient addresses passed on the
command-line, and writes it into the
.B E-MailRelay .B E-MailRelay
spool directory. spool directory.
.SH SEE ALSO .SH SEE ALSO

View File

@ -39,8 +39,8 @@ or to people who are on the same local area network.
Also be careful with programs like "fetchmail" which will fetch incoming mail Also be careful with programs like "fetchmail" which will fetch incoming mail
using POP3 or IMAP, but then use a local SMTP server to deliver to local using POP3 or IMAP, but then use a local SMTP server to deliver to local
mailboxes. If your local SMTP server is E-MailRelay then your incoming e-mail mailboxes. E-MailRelay does not act as a delivery agent, so if your local SMTP
will bounce back out. server is E-MailRelay then your incoming e-mail will bounce back out.
Why use it? Why use it?
----------- -----------

View File

@ -1,10 +1,10 @@
Summary: Simple e-mail message transfer agent using SMTP Summary: Simple e-mail message transfer agent using SMTP
Name: emailrelay Name: emailrelay
Version: 0.9.7 Version: 0.9.8
Release: 1 Release: 1
Copyright: GPL Copyright: GPL
Group: System Environment/Daemons Group: System Environment/Daemons
Source: http://emailrelay.sourceforge.net/.../emailrelay-src-0.9.7.tar.gz Source: http://emailrelay.sourceforge.net/.../emailrelay-src-0.9.8.tar.gz
BuildRoot: /tmp/emailrelay-install BuildRoot: /tmp/emailrelay-install
%description %description
@ -35,7 +35,9 @@ rm -rf $RPM_BUILD_ROOT
%files %files
/usr/local/libexec/emailrelay-passwd
/usr/local/libexec/emailrelay-poke /usr/local/libexec/emailrelay-poke
/usr/local/libexec/emailrelay-submit
/usr/local/libexec/emailrelay.sh /usr/local/libexec/emailrelay.sh
/usr/local/sbin/emailrelay /usr/local/sbin/emailrelay
/usr/local/var/spool/emailrelay/empty_file /usr/local/var/spool/emailrelay/empty_file
@ -49,9 +51,10 @@ rm -rf $RPM_BUILD_ROOT
/usr/local/share/emailrelay/man.html /usr/local/share/emailrelay/man.html
/usr/local/share/emailrelay/index.html /usr/local/share/emailrelay/index.html
/usr/local/share/emailrelay/windows.html /usr/local/share/emailrelay/windows.html
/usr/local/share/emailrelay/changelog.html
/usr/local/share/emailrelay/graphics/bullet.gif /usr/local/share/emailrelay/graphics/bullet.gif
/usr/local/share/emailrelay/html/
/usr/local/man/man1/emailrelay.1 /usr/local/man/man1/emailrelay.1
/usr/local/man/man1/emailrelay-passwd.1
/usr/local/man/man1/emailrelay-poke.1 /usr/local/man/man1/emailrelay-poke.1
%changelog %changelog

View File

@ -296,7 +296,8 @@ gpath.o: gpath.cpp gdef.h ../../config.h ../../lib/gcc2.95/iostream \
gprocess_unix.o: gprocess_unix.cpp gdef.h ../../config.h \ gprocess_unix.o: gprocess_unix.cpp gdef.h ../../config.h \
../../lib/gcc2.95/iostream ../../lib/gcc2.95/sstream \ ../../lib/gcc2.95/iostream ../../lib/gcc2.95/sstream \
../../lib/gcc2.95/xlocale ../../lib/gcc2.95/limits gprocess.h \ ../../lib/gcc2.95/xlocale ../../lib/gcc2.95/limits gprocess.h \
gexception.h gpath.h gstrings.h gfs.h glog.h gexception.h gpath.h gstrings.h gassert.h glogoutput.h glog.h \
gfs.h
groot.o: groot.cpp gdef.h ../../config.h ../../lib/gcc2.95/iostream \ groot.o: groot.cpp gdef.h ../../config.h ../../lib/gcc2.95/iostream \
../../lib/gcc2.95/sstream ../../lib/gcc2.95/xlocale \ ../../lib/gcc2.95/sstream ../../lib/gcc2.95/xlocale \
../../lib/gcc2.95/limits groot.h gprocess.h gexception.h \ ../../lib/gcc2.95/limits groot.h gprocess.h gexception.h \

View File

@ -94,33 +94,16 @@ public:
// error. // error.
static Who fork() ; static Who fork() ;
// Forks a new process. // Forks a child process.
static Who fork( Id & child ) ; static Who fork( Id & child ) ;
// Forks a new process. In the parent process // Forks a child process. Returns the child
// the child process-id is returned by reference. // pid to the parent.
static void exec( const Path & exe , const std::string & arg = std::string() ) ; static int spawn( Identity nobody , const Path & exe , const std::string & arg , int error_return = 127 ) ;
// Executes a program taking reasonable security // Runs a command in an unprivileged child process. Returns the
// precautions.
static int wait( const Id & child ) ;
// Waits for a child process to terminate.
// Returns the exit code. Throws exceptions
// on error.
static int wait( const Id & child , int error_return ) ;
// Waits for a child process to terminate.
// Returns the exit code, or returns 'error_return'
// on error.
static int spawn( const Path & exe , const std::string & arg , int error_return = 127 ) ;
// Runs a command in a child process. Returns the
// child process's exit code, or 'error_return' on error. // child process's exit code, or 'error_return' on error.
// The identity should have come from beOrdinary().
static bool privileged() ;
// Returns true if this process has enhanced security
// privileges.
static int errno_() ; static int errno_() ;
// Returns the process's current 'errno' value. // Returns the process's current 'errno' value.
@ -142,7 +125,10 @@ public:
private: private:
Process() ; Process() ;
static int wait( const Id & child ) ;
static int wait( const Id & child , int error_return ) ;
static void execCore( const Path & , const std::string & ) ; static void execCore( const Path & , const std::string & ) ;
static void beNobody( Identity ) ;
} ; } ;
namespace G namespace G

View File

@ -23,6 +23,7 @@
#include "gdef.h" #include "gdef.h"
#include "gprocess.h" #include "gprocess.h"
#include "gassert.h"
#include "gfs.h" #include "gfs.h"
#include "glog.h" #include "glog.h"
#include <errno.h> #include <errno.h>
@ -176,17 +177,27 @@ int G::Process::errno_()
return errno ; // not ::errno or std::errno for gcc2.95 return errno ; // not ::errno or std::errno for gcc2.95
} }
void G::Process::exec( const G::Path & exe , const std::string & arg ) int G::Process::spawn( Identity nobody , const G::Path & exe , const std::string & arg , int error_return )
{ {
if( exe.isRelative() ) if( exe.isRelative() )
throw InvalidPath( exe.str() ) ; throw InvalidPath( exe.str() ) ;
if( privileged() ) if( ::geteuid() == 0U || nobody.uid == 0U )
throw Insecure() ; throw Insecure() ;
closeFiles() ; Id child_pid ;
if( fork(child_pid) == Child )
execCore( exe , arg ) ; {
beNobody( nobody ) ;
G_ASSERT( ::getuid() != 0U && ::geteuid() != 0U ) ;
closeFiles() ;
execCore( exe , arg ) ;
::_exit( error_return ) ;
}
else
{
return wait( child_pid , error_return ) ;
}
} }
void G::Process::execCore( const G::Path & exe , const std::string & arg ) void G::Process::execCore( const G::Path & exe , const std::string & arg )
@ -209,31 +220,6 @@ void G::Process::execCore( const G::Path & exe , const std::string & arg )
G_WARNING( "G::Process::exec: execve() returned: errno=" << error << ": " << exe ) ; G_WARNING( "G::Process::exec: execve() returned: errno=" << error << ": " << exe ) ;
} }
int G::Process::spawn( const G::Path & exe , const std::string & arg , int error_return )
{
if( exe.isRelative() )
throw InvalidPath( exe.str() ) ;
if( privileged() )
throw Insecure() ;
Id child_pid ;
if( fork(child_pid) == Child )
{
exec( exe , arg ) ;
::_exit( error_return ) ;
}
else
{
return wait( child_pid , error_return ) ;
}
}
bool G::Process::privileged()
{
return ::getuid() == 0U || ::geteuid() == 0U ;
}
void G::Process::beSpecial( Identity identity ) void G::Process::beSpecial( Identity identity )
{ {
// try to change our effective id -- this // try to change our effective id -- this
@ -270,6 +256,17 @@ G::Process::Identity G::Process::beOrdinary( Identity nobody )
return special_identity ; return special_identity ;
} }
void G::Process::beNobody( Identity nobody )
{
if( ::getuid() == 0 )
{
// set the *real* userid
if( ::seteuid(0) ) throw UidError("0") ; // first
if( ::setgid(nobody.gid) ) throw GidError(nobody.str()) ; // second
if( ::setuid(nobody.uid) ) throw UidError(nobody.str()) ; // third
}
}
// === // ===
G::Process::Id::Id() : m_imp(NULL) G::Process::Id::Id() : m_imp(NULL)

View File

@ -59,9 +59,16 @@ G::Root::~Root()
} }
} }
//static
void G::Root::init( const std::string & nobody ) void G::Root::init( const std::string & nobody )
{ {
m_nobody = G::Process::Identity( nobody ) ; m_nobody = Process::Identity( nobody ) ;
m_special = Process::beOrdinary( m_nobody ) ; m_special = Process::beOrdinary( m_nobody ) ;
} }
//static
G::Process::Identity G::Root::nobody()
{
return m_nobody ;
}

View File

@ -54,6 +54,10 @@ public:
// gives a non-privileged username which // gives a non-privileged username which
// is used if the real user-id is root. // is used if the real user-id is root.
static Process::Identity nobody() ;
// Returns the 'nobody' identity.
// Precondition: init() called
private: private:
void * operator new( size_t ) ; void * operator new( size_t ) ;

View File

@ -3,7 +3,7 @@
# General configuration options # General configuration options
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
PROJECT_NAME = E-MailRelay PROJECT_NAME = E-MailRelay
PROJECT_NUMBER = 0.9.7 PROJECT_NUMBER = 0.9.8
OUTPUT_DIRECTORY = OUTPUT_DIRECTORY =
OUTPUT_LANGUAGE = English OUTPUT_LANGUAGE = English
EXTRACT_ALL = YES EXTRACT_ALL = YES
@ -67,7 +67,7 @@ EXCLUDE = src/glib/gdef.h gdef.h \
src/gnet/gresolve_ipv6.cpp gresolve_ipv6.cpp \ src/gnet/gresolve_ipv6.cpp gresolve_ipv6.cpp \
src/gnet/grequest.cpp grequest.cpp \ src/gnet/grequest.cpp grequest.cpp \
src/gnet/grequest.h grequest.h src/gnet/grequest.h grequest.h
EXCLUDE_PATTERNS = */*_win32.* */*_ipv6.cpp */old/* */resolverd.cpp EXCLUDE_PATTERNS = */*_win32.* */*_ipv6.cpp */old/* */resolverd.cpp */gsasl_cyrus.cpp
EXAMPLE_PATH = EXAMPLE_PATH =
EXAMPLE_PATTERNS = EXAMPLE_PATTERNS =
IMAGE_PATH = IMAGE_PATH =

View File

@ -325,10 +325,10 @@ void GSmtp::ClientProtocol::onTimeout()
G::Strings GSmtp::ClientProtocol::serverAuthMechanisms( const ClientProtocolReply & reply ) const G::Strings GSmtp::ClientProtocol::serverAuthMechanisms( const ClientProtocolReply & reply ) const
{ {
G::Strings result ; G::Strings result ;
std::string auth_line = reply.textLine("AUTH") ; std::string auth_line = reply.textLine("AUTH ") ; // trailing space to avoid "AUTH="
if( ! auth_line.empty() ) if( ! auth_line.empty() )
{ {
G::Str::splitIntoTokens( auth_line , result , " \t" ) ; G::Str::splitIntoTokens( auth_line , result , " " ) ;
if( result.size() ) if( result.size() )
result.pop_front() ; // remove "AUTH" ; result.pop_front() ; // remove "AUTH" ;
} }
@ -415,6 +415,7 @@ GSmtp::ClientProtocolReply::ClientProtocolReply( const std::string & line ) :
{ {
m_text = line.substr(4U) ; m_text = line.substr(4U) ;
G::Str::trimLeft( m_text , " \t" ) ; G::Str::trimLeft( m_text , " \t" ) ;
G::Str::replaceAll( m_text , "\t" , " " ) ;
} }
} }
} }

View File

@ -78,18 +78,49 @@ public:
BadSequence_503 = 503 , BadSequence_503 = 503 ,
Invalid = 0 , Invalid = 0 ,
} ; } ;
explicit ClientProtocolReply( const std::string & line = std::string() ) ; explicit ClientProtocolReply( const std::string & line = std::string() ) ;
bool incomplete() const ; // Constructor for one line of text.
bool add( const ClientProtocolReply & other ) ; bool add( const ClientProtocolReply & other ) ;
// Adds more lines to this reply. Returns
// false if the numeric values are different.
bool incomplete() const ;
// Returns true if the reply is incomplete.
bool validFormat() const ; bool validFormat() const ;
bool positive() const ; // <400 // Returns true if
bool positive() const ;
// Returns true if the numeric value of the
// reply is less that four hundred.
bool is( Value v ) const ; bool is( Value v ) const ;
// Returns true if the reply value is 'v'.
unsigned int value() const ; unsigned int value() const ;
// Returns the numeric value of the reply.
std::string text() const ; std::string text() const ;
std::string textLine( const std::string & prefix ) const ; // Returns the complete text of the reply,
Type type() const ; // excluding the numeric part, and with
SubType subType() const ; // embedded newlines.
bool textContains( std::string s ) const ; bool textContains( std::string s ) const ;
// Returns true if the text() contains
// the given substring.
std::string textLine( const std::string & prefix ) const ;
// Returns a line of text() which starts with
// prefix.
Type type() const ;
// Returns the reply type (category).
SubType subType() const ;
// Returns the reply sub-type.
private: private:
bool m_complete ; bool m_complete ;
bool m_valid ; bool m_valid ;

View File

@ -164,7 +164,7 @@ 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 )
{ {
G_LOG( "GSmtp::NewFile::preprocess: " << m_preprocessor << " " << path ) ; G_LOG( "GSmtp::NewFile::preprocess: " << m_preprocessor << " " << path ) ;
int exit_code = G::Process::spawn( m_preprocessor , path.str() ) ; int exit_code = G::Process::spawn( G::Root::nobody() , m_preprocessor , path.str() ) ;
G_LOG( "GSmtp::NewFile::preprocess: exit status " << exit_code ) ; G_LOG( "GSmtp::NewFile::preprocess: exit status " << exit_code ) ;
return exit_code ; return exit_code ;
} }

View File

@ -34,7 +34,7 @@ namespace Main
} ; } ;
// Class: Main::Legal // Class: Main::Legal
// Description: // Description: A static class providing warranty and copyright text.
// //
class Main::Legal class Main::Legal
{ {

View File

@ -48,7 +48,7 @@
//static //static
std::string Main::Run::versionNumber() std::string Main::Run::versionNumber()
{ {
return "0.9.7" ; return "0.9.8" ;
} }
Main::Run::Run( const G::Arg & arg ) : Main::Run::Run( const G::Arg & arg ) :

View File

@ -24,7 +24,7 @@
// directory. // directory.
// //
// It works a bit like sendmail... // It works a bit like sendmail...
// * additional recipient addresses can be put on the command-line // * envelope recipient addresses are taken from the command-line
// * content (header+body) is read from stdin up to "." or EOF // * content (header+body) is read from stdin up to "." or EOF
// * the envelope "From" field can be specified on the command-line // * the envelope "From" field can be specified on the command-line
// * the envelope "From" field is defaulted from the header "From:" line // * the envelope "From" field is defaulted from the header "From:" line
@ -42,11 +42,14 @@
#include "gpath.h" #include "gpath.h"
#include "gfilestore.h" #include "gfilestore.h"
#include "gnewmessage.h" #include "gnewmessage.h"
#include "gexception.h"
#include "legal.h" #include "legal.h"
#include <exception> #include <exception>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
G_EXCEPTION( NoBody , "no body text" ) ;
static void process( const G::Path & path , std::istream & stream , static void process( const G::Path & path , std::istream & stream ,
const G::Strings & to_list , std::string from , G::Strings header ) const G::Strings & to_list , std::string from , G::Strings header )
{ {
@ -76,26 +79,14 @@ static void process( const G::Path & path , std::istream & stream ,
GSmtp::FileStore store( path ) ; GSmtp::FileStore store( path ) ;
std::auto_ptr<GSmtp::NewMessage> msg = store.newMessage( envelope_from ) ; std::auto_ptr<GSmtp::NewMessage> msg = store.newMessage( envelope_from ) ;
// add additional "To:" lines to the header // add "To:" lines to the envelope
// //
for( G::Strings::const_iterator to_p = to_list.begin() ; to_p != to_list.end() ; ++to_p ) for( G::Strings::const_iterator to_p = to_list.begin() ; to_p != to_list.end() ; ++to_p )
{ {
header.push_back( std::string("To: ") + *to_p ) ; std::string to = *to_p ;
} G::Str::trim( to , " \t\r\n" ) ;
const bool is_local = false ;
// add "To:" lines to the envelope msg->addTo( to , is_local ) ;
{
for( G::Strings::const_iterator header_p = header.begin() ; header_p != header.end() ; ++header_p )
{
const std::string & line = *header_p ;
if( line.find("To: ") == 0U )
{
std::string to = line.substr(3U) ;
G::Str::trim( to , " \t\r\n" ) ;
const bool is_local = false ;
msg->addTo( to , is_local ) ;
}
}
} }
// stream out the header // stream out the header
@ -145,7 +136,7 @@ static void run( const G::Arg & arg )
else if( getopt.contains("help") ) else if( getopt.contains("help") )
{ {
std::ostream & stream = std::cerr ; std::ostream & stream = std::cerr ;
getopt.showUsage( stream , arg.prefix() , " [<to-address> ...]" ) ; getopt.showUsage( stream , arg.prefix() , " <to-address> [<to-address> ...]" ) ;
stream stream
<< std::endl << std::endl
<< Main::Legal::warranty() << Main::Legal::warranty()
@ -153,6 +144,12 @@ static void run( const G::Arg & arg )
<< Main::Legal::copyright() << Main::Legal::copyright()
<< std::endl ; << std::endl ;
} }
else if( getopt.args().c() == 1U )
{
std::cerr
<< getopt.usageSummary( arg.prefix() , " <to-address> [<to-address> ...]" )
<< std::endl ;
}
else else
{ {
G::Path path = GSmtp::MessageStore::defaultDirectory() ; G::Path path = GSmtp::MessageStore::defaultDirectory() ;
@ -181,6 +178,8 @@ static void run( const G::Arg & arg )
while( stream.good() ) while( stream.good() )
{ {
std::string line = G::Str::readLineFrom( stream ) ; std::string line = G::Str::readLineFrom( stream ) ;
if( line == "." )
throw NoBody() ;
if( line.empty() ) if( line.empty() )
break ; break ;
header.push_back( line ) ; header.push_back( line ) ;