E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

gstoredfile.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 // gstoredfile.cpp
19 //
20
21 #include "gdef.h"
22 #include "gsmtp.h"
23 #include "gfilestore.h"
24 #include "gstoredfile.h"
25 #include "gxtext.h"
26 #include "gfile.h"
27 #include "gstr.h"
28 #include "glog.h"
29 #include "gassert.h"
30 #include <fstream>
31
32 GSmtp::StoredFile::StoredFile( FileStore & store , const G::Path & envelope_path ) :
33 m_store(store) ,
34 m_envelope_path(envelope_path) ,
35 m_eight_bit(false) ,
36 m_errors(0U) ,
37 m_locked(false) ,
38 m_crlf(false)
39 {
40 m_name = m_envelope_path.basename() ;
41 size_t pos = m_name.rfind(".envelope") ;
42 if( pos != std::string::npos ) m_name.erase( pos ) ;
43 G_DEBUG( "GSmtp::StoredFile::ctor: name=[" << m_name << "]" ) ;
44 }
45
46 GSmtp::StoredFile::~StoredFile()
47 {
48 try
49 {
50 if( m_locked )
51 {
52 FileWriter claim_writer ;
53 G::File::rename( m_envelope_path , m_old_envelope_path , G::File::NoThrow() ) ;
54 }
55 }
56 catch(...) // dtor
57 {
58 }
59 }
60
61 std::string GSmtp::StoredFile::name() const
62 {
63 return m_name ;
64 }
65
66 std::string GSmtp::StoredFile::location() const
67 {
68 return contentPath().str() ;
69 }
70
71 bool GSmtp::StoredFile::eightBit() const
72 {
73 return m_eight_bit ;
74 }
75
76 void GSmtp::StoredFile::sync( bool )
77 {
78 readEnvelopeCore( true ) ;
79 }
80
81 bool GSmtp::StoredFile::readEnvelope( std::string & reason , bool check_recipients )
82 {
83 try
84 {
85 readEnvelopeCore( check_recipients ) ;
86 return true ;
87 }
88 catch( std::exception & e ) // invalid file in store
89 {
90 reason = e.what() ;
91 return false ;
92 }
93 }
94
95 void GSmtp::StoredFile::readEnvelopeCore( bool check_recipients )
96 {
97 FileReader claim_reader ;
98 std::ifstream stream( m_envelope_path.str().c_str() , std::ios_base::binary | std::ios_base::in ) ;
99 if( ! stream.good() )
100 throw ReadError( m_envelope_path.str() ) ;
101
102 m_crlf = false ;
103 readFormat( stream ) ;
104 readFlag( stream ) ;
105 readFrom( stream ) ;
106 readToList( stream ) ;
107 readAuthentication( stream ) ;
108 readClientSocketAddress( stream ) ;
109 if( m_format == FileStore::format() )
110 {
111 readClientCertificate( stream ) ;
112 readFromAuthIn( stream ) ;
113 readFromAuthOut( stream ) ;
114 }
115 else if( m_format == FileStore::format(-1) )
116 {
117 readClientSocketName( stream ) ;
118 readClientCertificate( stream ) ;
119 }
120 readEnd( stream ) ;
121
122 if( check_recipients && m_to_remote.size() == 0U )
123 throw FormatError( "no recipients" ) ;
124
125 if( ! stream.good() )
126 throw ReadError( m_envelope_path.str() ) ;
127
128 readReasons( stream ) ;
129 }
130
131 void GSmtp::StoredFile::readFormat( std::istream & stream )
132 {
133 m_format = readValue( stream , "Format" ) ;
134 if( ! FileStore::knownFormat(m_format) )
135 throw FormatError( "unknown format id" , m_format ) ;
136 }
137
138 void GSmtp::StoredFile::readFlag( std::istream & stream )
139 {
140 m_eight_bit = readValue(stream,"Content") == "8bit" ;
141 }
142
143 void GSmtp::StoredFile::readFrom( std::istream & stream )
144 {
145 m_from = readValue( stream , "From" ) ;
146 G_DEBUG( "GSmtp::StoredFile::readFrom: from \"" << m_from << "\"" ) ;
147 }
148
149 void GSmtp::StoredFile::readFromAuthIn( std::istream & stream )
150 {
151 m_from_auth_in = readValue( stream , "MailFromAuthIn" ) ;
152 if( !m_from_auth_in.empty() && m_from_auth_in != "+" && !G::Xtext::valid(m_from_auth_in) )
153 throw FormatError( "invalid mail-from-auth-in encoding" ) ;
154 }
155
156 void GSmtp::StoredFile::readFromAuthOut( std::istream & stream )
157 {
158 m_from_auth_out = readValue( stream , "MailFromAuthOut" ) ;
159 if( !m_from_auth_out.empty() && m_from_auth_out != "+" && !G::Xtext::valid(m_from_auth_out) )
160 throw FormatError( "invalid mail-from-auth-out encoding" ) ;
161 }
162
163 void GSmtp::StoredFile::readToList( std::istream & stream )
164 {
165 m_to_local.clear() ;
166 m_to_remote.clear() ;
167
168 unsigned int to_count = G::Str::toUInt( readValue(stream,"ToCount") ) ;
169
170 for( unsigned int i = 0U ; i < to_count ; i++ )
171 {
172 std::string to_line = readLine( stream ) ;
173 bool is_local = to_line.find(FileStore::x()+"To-Local: ") == 0U ;
174 bool is_remote = to_line.find(FileStore::x()+"To-Remote: ") == 0U ;
175 if( ! is_local && ! is_remote )
176 throw FormatError( "bad 'to' line" ) ;
177
178 if( is_local )
179 m_to_local.push_back( value(to_line) ) ;
180 else
181 m_to_remote.push_back( value(to_line) ) ;
182 }
183 }
184
185 void GSmtp::StoredFile::readAuthentication( std::istream & stream )
186 {
187 m_authentication = G::Xtext::decode( readValue(stream,"Authentication") ) ;
188 }
189
190 void GSmtp::StoredFile::readClientSocketAddress( std::istream & stream )
191 {
192 m_client_socket_address = readValue( stream , "Client" ) ;
193 }
194
195 void GSmtp::StoredFile::readClientSocketName( std::istream & stream )
196 {
197 m_client_socket_name = G::Xtext::decode( readValue(stream,"ClientName") ) ;
198 }
199
200 void GSmtp::StoredFile::readClientCertificate( std::istream & stream )
201 {
202 m_client_certificate = readValue( stream , "ClientCertificate" ) ;
203 }
204
205 void GSmtp::StoredFile::readEnd( std::istream & stream )
206 {
207 std::string end = readLine( stream ) ;
208 if( end.find(FileStore::x()+"End") != 0U )
209 throw FormatError( "no end line" ) ;
210 }
211
212 void GSmtp::StoredFile::readReasons( std::istream & stream )
213 {
214 m_errors = 0U ;
215 while( stream.good() )
216 {
217 std::string reason = readLine( stream ) ;
218 if( reason.find(FileStore::x()+"Reason") == 0U )
219 m_errors++ ;
220 }
221 }
222
223 bool GSmtp::StoredFile::openContent( std::string & reason )
224 {
225 try
226 {
227 FileReader claim_reader ;
228 G::Path content_path = contentPath() ;
229 G_DEBUG( "GSmtp::FileStore::openContent: \"" << content_path << "\"" ) ;
230 unique_ptr<std::istream> stream( new std::ifstream(
231 content_path.str().c_str() , std::ios_base::in | std::ios_base::binary ) ) ;
232 if( !stream->good() )
233 {
234 reason = "cannot open content file" ;
235 return false ;
236 }
237
238 G_DEBUG( "GSmtp::MessageStore: processing envelope \"" << m_envelope_path.basename() << "\"" ) ;
239 G_DEBUG( "GSmtp::MessageStore: processing content \"" << content_path.basename() << "\"" ) ;
240
241 m_content.reset( stream.release() ) ;
242 return true ;
243 }
244 catch( std::exception & e ) // invalid file in store
245 {
246 G_DEBUG( "GSmtp::FileStore: exception: " << e.what() ) ;
247 reason = e.what() ;
248 return false ;
249 }
250 }
251
252 std::string GSmtp::StoredFile::readLine( std::istream & stream )
253 {
254 std::string line = G::Str::readLineFrom( stream ) ;
255
256 // allow lf or crlf and stay consistent when appending
257 if( !line.empty() && line.at(line.size()-1U) == '\r' )
258 m_crlf = true ;
259 G::Str::trimRight( line , "\r" ) ;
260
261 return line ;
262 }
263
264 std::string GSmtp::StoredFile::readValue( std::istream & stream , const std::string & expected_key )
265 {
266 std::string line = readLine( stream ) ;
267
268 std::string prefix = FileStore::x() + expected_key + ":" ;
269 if( line == prefix )
270 return std::string() ;
271
272 prefix.append( 1U , ' ' ) ;
273 size_t pos = line.find( prefix ) ;
274 if( pos != 0U )
275 throw FormatError( "expected \"" + FileStore::x() + expected_key + ":\"" ) ;
276
277 // RFC-2822 unfolding
278 for(;;)
279 {
280 char c = stream.peek() ;
281 if( c == ' ' || c == '\t' )
282 line.append( readLine(stream) ) ;
283 else
284 break ;
285 }
286
287 return value( line ) ;
288 }
289
290 std::string GSmtp::StoredFile::value( const std::string & line ) const
291 {
292 return G::Str::trimmed( G::Str::tail( line , line.find(":") , std::string() ) , G::Str::ws() ) ;
293 }
294
295 const std::string & GSmtp::StoredFile::eol() const
296 {
297 static const std::string crlf( "\r\n" ) ;
298 static const std::string lf( "\n" ) ;
299 return m_crlf ? crlf : lf ;
300 }
301
302 bool GSmtp::StoredFile::lock()
303 {
304 const G::Path src = m_envelope_path ;
305 const G::Path dst( src.str() + ".busy" ) ;
306 bool ok = false ;
307 {
308 FileWriter claim_writer ;
309 ok = G::File::rename( src , dst , G::File::NoThrow() ) ;
310 }
311 if( ok )
312 {
313 G_LOG( "GSmtp::StoredMessage: locking file \"" << src.basename() << "\"" ) ;
314 m_envelope_path = dst ;
315 m_old_envelope_path = src ;
316 m_locked = true ;
317 }
318 m_store.updated() ;
319 return ok ;
320 }
321
322 void GSmtp::StoredFile::unlock()
323 {
324 if( m_locked )
325 {
326 {
327 FileWriter claim_writer ;
328 G::File::rename( m_envelope_path , m_old_envelope_path ) ;
329 }
330 G_LOG( "GSmtp::StoredMessage: unlocking file \"" << m_envelope_path.basename() << "\"" ) ;
331 m_envelope_path = m_old_envelope_path ;
332 m_locked = false ;
333 m_store.updated() ;
334 }
335 }
336
337 void GSmtp::StoredFile::fail( const std::string & reason , int reason_code )
338 {
339 if( G::File::exists(m_envelope_path) ) // client-side preprocessing may have removed it
340 {
341 addReason( m_envelope_path , reason , reason_code ) ;
342
343 G::Path bad_path = badPath( m_envelope_path ) ;
344 G_LOG_S( "GSmtp::StoredMessage: failing file: "
345 << "\"" << m_envelope_path.basename() << "\" -> "
346 << "\"" << bad_path.basename() << "\"" ) ;
347
348 FileWriter claim_writer ;
349 G::File::rename( m_envelope_path , bad_path , G::File::NoThrow() ) ;
350 }
351 }
352
353 void GSmtp::StoredFile::unfail()
354 {
355 G_DEBUG( "GSmtp::StoredMessage: unfailing file: " << m_envelope_path ) ;
356 if( m_envelope_path.extension() == "bad" )
357 {
358 G::Path dst = m_envelope_path.withoutExtension() ; // "foo.envelope.bad" -> "foo.envelope"
359 bool ok = false ;
360 {
361 FileWriter claim_writer ;
362 ok = G::File::rename( m_envelope_path , dst , G::File::NoThrow() ) ;
363 }
364 if( ok )
365 {
366 G_LOG( "GSmtp::StoredMessage: unfailed file: "
367 << "\"" << m_envelope_path.basename() << "\" -> "
368 << "\"" << dst.basename() << "\"" ) ;
369 m_envelope_path = dst ;
370 }
371 else
372 {
373 G_WARNING( "GSmtp::StoredMessage: failed to unfail file: \"" << m_envelope_path << "\"" ) ;
374 }
375 }
376 }
377
378 void GSmtp::StoredFile::addReason( const G::Path & path , const std::string & reason , int reason_code ) const
379 {
380 FileWriter claim_writer ;
381 std::ofstream file( path.str().c_str() ,
382 std::ios_base::binary | std::ios_base::app ) ; // "app", not "ate", for win32
383 file << FileStore::x() << "Reason: " << reason << eol() ;
384 file << FileStore::x() << "ReasonCode:" ; if( reason_code ) file << " " << reason_code ; file << eol() ;
385 }
386
387 G::Path GSmtp::StoredFile::badPath( G::Path busy_path )
388 {
389 return busy_path.withExtension("bad") ; ; // "foo.envelope.busy" -> "foo.envelope.bad"
390 }
391
392 void GSmtp::StoredFile::destroy()
393 {
394 G_LOG( "GSmtp::StoredMessage: deleting file: \"" << m_envelope_path.basename() << "\"" ) ;
395 {
396 FileWriter claim_writer ;
397 G::File::remove( m_envelope_path , G::File::NoThrow() ) ;
398 }
399
400 G::Path content_path = contentPath() ;
401 G_LOG( "GSmtp::StoredMessage: deleting file: \"" << content_path.basename() << "\"" ) ;
402 m_content.reset() ; // close it first (but not much good if it's been extracted already)
403 {
404 FileWriter claim_writer ;
405 G::File::remove( content_path , G::File::NoThrow() ) ;
406 }
407 }
408
409 const std::string & GSmtp::StoredFile::from() const
410 {
411 return m_from ;
412 }
413
414 const G::StringArray & GSmtp::StoredFile::to() const
415 {
416 return m_to_remote ;
417 }
418
419 unique_ptr<std::istream> GSmtp::StoredFile::extractContentStream()
420 {
421 G_ASSERT( m_content.get() != nullptr ) ;
422 return unique_ptr<std::istream>( m_content.release() ) ;
423 }
424
425 G::Path GSmtp::StoredFile::contentPath() const
426 {
427 std::string filename = m_envelope_path.str() ;
428
429 size_t pos = filename.rfind( ".envelope" ) ; // also works for ".envelope.bad" etc.
430 if( pos == std::string::npos )
431 throw FilenameError( filename ) ;
432
433 return G::Path( G::Str::head(filename,pos,std::string()) + ".content" ) ;
434 }
435
436 size_t GSmtp::StoredFile::remoteRecipientCount() const
437 {
438 return m_to_remote.size() ;
439 }
440
441 size_t GSmtp::StoredFile::errorCount() const
442 {
443 return m_errors ;
444 }
445
446 std::string GSmtp::StoredFile::authentication() const
447 {
448 return m_authentication ;
449 }
450
451 std::string GSmtp::StoredFile::fromAuthIn() const
452 {
453 return m_from_auth_in ;
454 }
455
456 std::string GSmtp::StoredFile::fromAuthOut() const
457 {
458 return m_from_auth_out ;
459 }
460
461 /// \file gstoredfile.cpp
GSmtp::StoredFile::readEnvelope
bool readEnvelope(std::string &reason, bool check_for_no_remote_recipients)
Reads the envelope.
Definition: gstoredfile.cpp:81
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
GSmtp::StoredFile::unfail
virtual void unfail() override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:353
GSmtp::FileStore::format
static std::string format(int generation=0)
Returns an identifier for the storage format implemented by this class, or some older generation of i...
Definition: gfilestore.cpp:130
GSmtp::FileStore::x
static std::string x()
Returns the prefix for envelope header lines.
Definition: gfilestore.cpp:125
G::Str::tail
static std::string tail(const std::string &in, size_type pos, const std::string &default_=std::string())
Returns the last part of the string after the given position.
Definition: gstr.cpp:1102
G::Path::withoutExtension
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:328
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
GSmtp::StoredFile::from
virtual const std::string & from() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:409
G::Str::head
static std::string head(const std::string &in, size_type pos, const std::string &default_=std::string())
Returns the first part of the string up to just before the given position.
Definition: gstr.cpp:1088
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
GSmtp::StoredFile::extractContentStream
virtual unique_ptr< std::istream > extractContentStream() override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:419
GSmtp::StoredFile::fromAuthIn
virtual std::string fromAuthIn() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:451
GSmtp::FileWriter
Used by GSmtp::FileStore, GSmtp::NewFile and GSmtp::StoredFile to claim write permissions.
Definition: gfilestore.h:197
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
GSmtp::StoredFile::destroy
virtual void destroy() override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:392
GSmtp::StoredFile::fail
virtual void fail(const std::string &reason, int reason_code) override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:337
GSmtp::StoredFile::location
virtual std::string location() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:66
GSmtp::StoredFile::authentication
virtual std::string authentication() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:446
GSmtp::StoredFile::errorCount
virtual size_t errorCount() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:441
G::Str::toUInt
static unsigned int toUInt(const std::string &s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:488
GSmtp::StoredFile::~StoredFile
virtual ~StoredFile()
Destructor.
Definition: gstoredfile.cpp:46
G::Xtext::valid
static bool valid(const std::string &)
Returns true if a valid encoding.
Definition: gxtext.cpp:72
gstr.h
gfilestore.h
gassert.h
G::Str::trimRight
static void trimRight(std::string &s, const std::string &ws, size_type limit=0U)
Trims the rhs of s, taking off up to 'limit' of the 'ws' characters.
Definition: gstr.cpp:235
GSmtp::StoredFile::openContent
bool openContent(std::string &reason)
Opens the content file.
Definition: gstoredfile.cpp:223
GSmtp::StoredFile::fromAuthOut
virtual std::string fromAuthOut() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:456
gfile.h
G::Str::trimmed
static std::string trimmed(const std::string &s, const std::string &ws)
Returns a trim()med version of s.
Definition: gstr.cpp:251
G::File::exists
static bool exists(const Path &file)
Returns true if the file (directory, device etc.) exists.
Definition: gfile.cpp:141
G::Str::readLineFrom
static std::string readLineFrom(std::istream &stream, const std::string &eol=std::string())
Reads a line from the stream using the given line terminator.
Definition: gstr.cpp:733
GSmtp::StoredFile::StoredFile
StoredFile(FileStore &store, const G::Path &envelope_path)
Constructor.
Definition: gstoredfile.cpp:32
G::File::remove
static bool remove(const Path &path, const NoThrow &)
Deletes the file or directory. Returns false on error.
Definition: gfile.cpp:29
GSmtp::StoredFile::remoteRecipientCount
virtual size_t remoteRecipientCount() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:436
gxtext.h
G::File::NoThrow
An overload discriminator class for File methods.
Definition: gfile.h:53
gstoredfile.h
GSmtp::FileStore
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:58
gsmtp.h
GSmtp::FileStore::knownFormat
static bool knownFormat(const std::string &format)
Returns true if the storage format string is recognised and supported for reading.
Definition: gfilestore.cpp:140
GSmtp::FileReader
Used by GSmtp::FileStore, GSmtp::NewFile and GSmtp::StoredFile to claim read permissions for reading ...
Definition: gfilestore.h:164
GSmtp::StoredFile::lock
bool lock()
Locks the file by renaming the envelope file.
Definition: gstoredfile.cpp:302
GSmtp::StoredFile::to
virtual const G::StringArray & to() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:414
GSmtp::StoredFile::name
virtual std::string name() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:61
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
GSmtp::StoredFile::sync
virtual void sync(bool) override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:76
GSmtp::StoredFile::eightBit
virtual bool eightBit() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:71
G::Path::withExtension
Path withExtension(const std::string &ext) const
Returns the path with the new basename extension.
Definition: gpath.cpp:346