E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

gnewfile.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2001-2018 Graeme Walker <graeme_walker@users.sourceforge.net>
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // ===
17 //
18 // gnewfile.cpp
19 //
20
21 #include "gdef.h"
22 #include "gsmtp.h"
23 #include "gmessagestore.h"
24 #include "gnewfile.h"
25 #include "gprocess.h"
26 #include "gstrings.h"
27 #include "groot.h"
28 #include "gfile.h"
29 #include "gstr.h"
30 #include "gxtext.h"
31 #include "gassert.h"
32 #include "glog.h"
33 #include <functional>
34 #include <algorithm>
35 #include <iostream>
36 #include <fstream>
37
38 GSmtp::NewFile::NewFile( FileStore & store , const std::string & from ,
39 const std::string & from_auth_in , const std::string & from_auth_out ,
40 size_t max_size ) :
41 m_store(store) ,
42 m_from(from) ,
43 m_from_auth_in(from_auth_in) ,
44 m_from_auth_out(from_auth_out) ,
45 m_committed(false) ,
46 m_eight_bit(false) ,
47 m_saved(false) ,
48 m_size(0UL) ,
49 m_max_size(max_size)
50 {
51 // ask the store for a unique id
52 //
53 m_seq = store.newSeq() ;
54
55 // ask the store for a content stream
56 //
57 m_content_path = m_store.contentPath( m_seq ) ;
58 G_LOG( "GSmtp::NewMessage: content file: " << m_content_path ) ;
59 unique_ptr<std::ostream> content_stream = m_store.stream( m_content_path ) ;
60 m_content.reset( content_stream.release() ) ;
61 }
62
63 GSmtp::NewFile::~NewFile()
64 {
65 try
66 {
67 G_DEBUG( "GSmtp::NewFile::dtor: " << m_content_path ) ;
68 cleanup() ;
69 m_store.updated() ;
70 }
71 catch(...) // dtor
72 {
73 }
74 }
75
76 void GSmtp::NewFile::cleanup()
77 {
78 discardContent() ;
79 if( ! m_committed )
80 {
81 deleteEnvelope() ;
82 deleteContent() ;
83 }
84 }
85
86 std::string GSmtp::NewFile::prepare( const std::string & session_auth_id , const std::string & peer_socket_address ,
87 const std::string & peer_certificate )
88 {
89 // flush and close the content file
90 //
91 flushContent() ;
92
93 // write the envelope
94 //
95 m_envelope_path_0 = m_store.envelopeWorkingPath( m_seq ) ;
96 m_envelope_path_1 = m_store.envelopePath( m_seq ) ;
97 if( ! saveEnvelope( session_auth_id , peer_socket_address , peer_certificate ) )
98 throw FileError( "cannot write envelope file " + m_envelope_path_0.str() ) ;
99
100 // deliver to local mailboxes
101 //
102 if( m_to_local.size() != 0U )
103 {
104 deliver( m_to_local , m_content_path , m_envelope_path_0 , m_envelope_path_1 ) ;
105 }
106
107 return m_content_path.str() ;
108 }
109
110 void GSmtp::NewFile::commit( bool strict )
111 {
112 m_committed = true ;
113 bool ok = commitEnvelope() ;
114 if( !ok && strict )
115 throw FileError( "cannot rename envelope file to " + m_envelope_path_1.str() ) ;
116 }
117
118 void GSmtp::NewFile::addTo( const std::string & to , bool local )
119 {
120 if( local )
121 m_to_local.push_back( to ) ;
122 else
123 m_to_remote.push_back( to ) ;
124 }
125
126 bool GSmtp::NewFile::addText( const char * line_data , size_t line_size )
127 {
128 m_size += static_cast<unsigned long>( line_size + 2U ) ;
129
130 // testing for eight-bit content can be slow, and _not_ testing
131 // the content is recommended by RFC-2821 ("a relay should ...
132 // relay [messages] without inspecting [the] content"), accepting
133 // the risk that a strictly-seven-bit server will reject or garble
134 // the message -- here we could assume eight-bit content and
135 // therefore ask servers for eightbitmime on everything (ignoring
136 // the question of mime-ness) -- if there are any seven-bit servers
137 // out there then some out-of-band processing could be used to edit
138 // the envelope file to the correct value before forwarding
139 //
140 //m_eight_bit = true ; // TODO maybe always assume content is eight-bit
141 m_eight_bit = m_eight_bit || isEightBit(line_data,line_size) ;
142
143 std::ostream & stream = *m_content.get() ;
144 stream.write( line_data , line_size ) ;
145 stream.write( "\r\n" , 2U ) ;
146
147 return m_max_size == 0UL || m_size < m_max_size ;
148 }
149
150 void GSmtp::NewFile::flushContent()
151 {
152 G_ASSERT( m_content.get() != nullptr ) ;
153 m_content->flush() ;
154 if( ! m_content->good() )
155 throw FileError( "cannot write content file " + m_content_path.str() ) ;
156 m_content.reset() ;
157 }
158
159 void GSmtp::NewFile::discardContent()
160 {
161 m_content.reset() ;
162 }
163
164 void GSmtp::NewFile::deleteContent()
165 {
166 FileWriter claim_writer ;
167 G::File::remove( m_content_path , G::File::NoThrow() ) ;
168 }
169
170 void GSmtp::NewFile::deleteEnvelope()
171 {
172 if( ! m_envelope_path_0.str().empty() )
173 {
174 FileWriter claim_writer ;
175 G::File::remove( m_envelope_path_0 , G::File::NoThrow() ) ;
176 }
177 }
178
179 namespace
180 {
181 struct EightBit : std::unary_function<char,bool>
182 {
183 bool operator()( char c ) { return !! ( static_cast<unsigned char>(c) & 0x80U ) ; }
184 } ;
185 }
186
187 bool GSmtp::NewFile::isEightBit( const char * line_data , size_t line_size )
188 {
189 const char * end = line_data + line_size ;
190 return std::find_if( line_data , end , EightBit() ) != end ;
191 }
192
193 bool GSmtp::NewFile::saveEnvelope( const std::string & session_auth_id , const std::string & peer_socket_address ,
194 const std::string & peer_certificate ) const
195 {
196 unique_ptr<std::ostream> envelope_stream = m_store.stream( m_envelope_path_0 ) ;
197 writeEnvelope( *(envelope_stream.get()) , m_envelope_path_0.str() ,
198 session_auth_id , peer_socket_address , peer_certificate ) ;
199 bool ok = envelope_stream->good() ;
200 return ok ;
201 }
202
203 bool GSmtp::NewFile::commitEnvelope()
204 {
205 FileWriter claim_writer ;
206 m_saved = G::File::rename( m_envelope_path_0 , m_envelope_path_1 , G::File::NoThrow() ) ;
207 return m_saved ;
208 }
209
210 void GSmtp::NewFile::deliver( const G::StringArray & /*to*/ ,
211 const G::Path & content_path , const G::Path & envelope_path_now ,
212 const G::Path & envelope_path_later )
213 {
214 // could shell out to "procmail" or "deliver" here, but keep it
215 // simple and within the scope -- just copy into ".local" files
216
217 G_LOG_S( "GSmtp::NewMessage: copying message for local recipient(s): "
218 << content_path.basename() << ".local" ) ;
219
220 FileWriter claim_writer ;
221 G::File::copy( content_path.str() , content_path.str()+".local" ) ;
222 G::File::copy( envelope_path_now.str() , envelope_path_later.str()+".local" ) ;
223 }
224
225 void GSmtp::NewFile::writeEnvelope( std::ostream & stream , const std::string & where ,
226 const std::string & session_auth_id , const std::string & peer_socket_address ,
227 const std::string & peer_certificate_in ) const
228 {
229 G_LOG( "GSmtp::NewMessage: envelope file: " << where ) ;
230
231 std::string peer_certificate = peer_certificate_in ;
232 G::Str::trim( peer_certificate , G::Str::ws() ) ;
233 G::Str::replaceAll( peer_certificate , "\r" , "" ) ;
234 G::Str::replaceAll( peer_certificate , "\n" , crlf()+std::string(1U,' ') ) ; // RFC-2822 folding
235
236 const std::string x( m_store.x() ) ;
237
238 stream << x << "Format: " << m_store.format() << crlf() ;
239 stream << x << "Content: " << (m_eight_bit?"8":"7") << "bit" << crlf() ;
240 stream << x << "From: " << m_from << crlf() ;
241 stream << x << "ToCount: " << (m_to_local.size()+m_to_remote.size()) << crlf() ;
242 {
243 G::StringArray::const_iterator to_p = m_to_local.begin() ;
244 for( ; to_p != m_to_local.end() ; ++to_p )
245 stream << x << "To-Local: " << *to_p << crlf() ;
246 }
247 {
248 G::StringArray::const_iterator to_p = m_to_remote.begin() ;
249 for( ; to_p != m_to_remote.end() ; ++to_p )
250 stream << x << "To-Remote: " << *to_p << crlf() ;
251 }
252 stream << x << "Authentication: " << G::Xtext::encode(session_auth_id) << crlf() ;
253 stream << x << "Client: " << peer_socket_address << crlf() ;
254 stream << x << "ClientCertificate: " << peer_certificate << crlf() ;
255 stream << x << "MailFromAuthIn: " << xnormalise(m_from_auth_in) << crlf() ;
256 stream << x << "MailFromAuthOut: " << xnormalise(m_from_auth_out) << crlf() ;
257 stream << x << "End: 1" << crlf() ;
258 stream.flush() ;
259 }
260
261 std::string GSmtp::NewFile::xnormalise( const std::string & s )
262 {
263 return G::Xtext::encode(G::Xtext::decode(s)) ;
264 }
265
266 const char * GSmtp::NewFile::crlf()
267 {
268 return "\r\n" ;
269 }
270
271 unsigned long GSmtp::NewFile::id() const
272 {
273 return m_seq ;
274 }
275
276 G::Path GSmtp::NewFile::contentPath() const
277 {
278 return m_content_path ;
279 }
280
281 /// \file gnewfile.cpp
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
GSmtp::FileStore::contentPath
G::Path contentPath(unsigned long seq) const
Returns the path for a content file.
Definition: gfilestore.cpp:182
GSmtp::NewFile::commit
virtual void commit(bool strict) override
Override from GSmtp::NewMessage.
Definition: gnewfile.cpp:110
GSmtp::NewFile::contentPath
G::Path contentPath() const
Returns the path of the content file.
Definition: gnewfile.cpp:276
gstrings.h
G::File::copy
static bool copy(const Path &from, const Path &to, const NoThrow &)
Copies a file. Returns false on error.
Definition: gfile.cpp:79
glog.h
G::Xtext::decode
static std::string decode(const std::string &)
Decodes the given string.
Definition: gxtext.cpp:107
G::Path::basename
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:310
G::Xtext::encode
static std::string encode(const std::string &)
Encodes the given string.
Definition: gxtext.cpp:86
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
groot.h
G::File::rename
static bool rename(const Path &from, const Path &to, const NoThrow &)
Renames the file. Returns false on error.
Definition: gfile.cpp:45
G::Str::trim
static void trim(std::string &s, const std::string &ws)
Trims both ends of s, taking off any of the 'ws' characters.
Definition: gstr.cpp:246
GSmtp::NewFile::~NewFile
virtual ~NewFile()
Destructor.
Definition: gnewfile.cpp:63
GSmtp::NewFile::NewFile
NewFile(FileStore &store, const std::string &from, const std::string &from_auth_in, const std::string &from_auth_out=std::string(), size_t max_size=0UL)
Constructor. The FileStore reference is kept.
Definition: gnewfile.cpp:38
GSmtp::FileStore::stream
unique_ptr< std::ostream > stream(const G::Path &path)
Returns a stream to the given content.
Definition: gfilestore.cpp:175
GSmtp::NewFile::id
virtual unsigned long id() const override
Override from GSmtp::NewMessage.
Definition: gnewfile.cpp:271
gstr.h
gassert.h
G::Str::replaceAll
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurances of sub-string 'from' with 'to'...
Definition: gstr.cpp:195
GSmtp::NewFile::addTo
virtual void addTo(const std::string &to, bool local) override
Override from GSmtp::NewMessage.
Definition: gnewfile.cpp:118
gfile.h
gprocess.h
gmessagestore.h
GSmtp::NewFile::addText
virtual bool addText(const char *, size_t) override
Override from GSmtp::NewMessage.
Definition: gnewfile.cpp:126
G::File::remove
static bool remove(const Path &path, const NoThrow &)
Deletes the file or directory. Returns false on error.
Definition: gfile.cpp:29
gxtext.h
G::File::NoThrow
An overload discriminator class for File methods.
Definition: gfile.h:53
GSmtp::FileStore
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:58
GSmtp::NewFile::prepare
virtual std::string prepare(const std::string &auth_id, const std::string &peer_socket_address, const std::string &peer_certificate) override
Override from GSmtp::NewMessage.
Definition: gnewfile.cpp:86
gsmtp.h
gnewfile.h
GSmtp::FileStore::newSeq
unsigned long newSeq()
Hands out a new non-zero sequence number.
Definition: gfilestore.cpp:212
G::Path
A Path object represents a file system path.
Definition: gpath.h:72
G::Str::ws
static std::string ws()
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1078