E-MailRelay
gstoredfile.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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/// \file gstoredfile.cpp
19///
20
21#include "gdef.h"
22#include "gfilestore.h"
23#include "gstoredfile.h"
24#include "gscope.h"
25#include "gfile.h"
26#include "gstr.h"
27#include "glog.h"
28#include "gassert.h"
29#include <fstream>
30
31GSmtp::StoredFile::StoredFile( FileStore & store , const G::Path & envelope_path ) :
32 m_store(store) ,
33 m_content(std::make_unique<std::ifstream>()) , // up-cast
34 m_id(MessageId::none()) ,
35 m_state(State::Normal)
36{
37 G_ASSERT( envelope_path.basename().find(".envelope") != std::string::npos ) ; // inc .bad
38 if( G::Str::tailMatch( envelope_path.basename() , ".bad" ) )
39 {
40 m_id = MessageId( envelope_path.withoutExtension().withoutExtension().basename() ) ;
41 m_state = State::Bad ;
42 }
43 else
44 {
45 m_id = MessageId( envelope_path.withoutExtension().basename() ) ;
46 }
47 G_DEBUG( "GSmtp::StoredFile::ctor: id=[" << m_id.str() << "]" ) ;
48}
49
51{
52 try
53 {
54 if( m_state == State::Locked )
55 {
56 // unlock
57 FileWriter claim_writer ;
58 G::File::rename( epath(State::Locked) , epath(State::Normal) , std::nothrow ) ;
59 }
60 }
61 catch(...) // dtor
62 {
63 }
64}
65
67{
68 return m_id ;
69}
70
71std::string GSmtp::StoredFile::location() const
72{
73 return cpath().str() ;
74}
75
76G::Path GSmtp::StoredFile::cpath() const
77{
78 return m_store.contentPath( m_id ) ;
79}
80
81G::Path GSmtp::StoredFile::epath( State state ) const
82{
83 if( state == State::Locked )
84 return m_store.envelopePath(m_id).str().append(".busy") ;
85 else if( state == State::Bad )
86 return m_store.envelopePath(m_id).str().append(".bad") ;
87 return m_store.envelopePath( m_id ) ;
88}
89
90int GSmtp::StoredFile::eightBit() const
91{
92 return m_env.m_eight_bit ;
93}
94
95void GSmtp::StoredFile::close()
96{
97 m_content.reset() ;
98}
99
100std::string GSmtp::StoredFile::reopen()
101{
102 std::string reason = "error" ;
103 if( !readEnvelope(reason,true) || !openContent(reason) )
104 return reason ;
105 else
106 return std::string() ;
107}
108
109bool GSmtp::StoredFile::readEnvelope( std::string & reason , bool check_recipients )
110{
111 try
112 {
113 readEnvelopeCore( check_recipients ) ;
114 return true ;
115 }
116 catch( std::exception & e ) // invalid file in store
117 {
118 reason = e.what() ;
119 return false ;
120 }
121}
122
123void GSmtp::StoredFile::readEnvelopeCore( bool check_recipients )
124{
125 std::ifstream stream ;
126 {
127 FileReader claim_reader ;
128 G::File::open( stream , epath(m_state) ) ;
129 }
130 if( ! stream.good() )
131 throw ReadError( epath(m_state).str() ) ;
132
133 GSmtp::Envelope::read( stream , m_env ) ;
134
135 if( check_recipients && m_env.m_to_remote.empty() )
136 throw FormatError( "no recipients" ) ;
137
138 if( ! stream.good() )
139 throw ReadError( epath(m_state).str() ) ;
140}
141
142bool GSmtp::StoredFile::openContent( std::string & reason )
143{
144 try
145 {
146 G_DEBUG( "GSmtp::FileStore::openContent: \"" << cpath() << "\"" ) ;
147 auto stream = std::make_unique<std::ifstream>() ;
148 {
149 FileReader claim_reader ;
150 G::File::open( *stream , cpath() ) ;
151 }
152 if( !stream->good() )
153 {
154 reason = "cannot open content file" ;
155 return false ;
156 }
157 stream->exceptions( std::ios_base::badbit ) ; // (new)
158 m_content = std::unique_ptr<std::istream>( stream.release() ) ; // up-cast
159 return true ;
160 }
161 catch( std::exception & e ) // invalid file in store
162 {
163 G_DEBUG( "GSmtp::FileStore: exception: " << e.what() ) ;
164 reason = e.what() ;
165 return false ;
166 }
167}
168
169const std::string & GSmtp::StoredFile::eol() const
170{
171 static const std::string crlf( "\r\n" ) ;
172 static const std::string lf( "\n" ) ;
173 return m_env.m_crlf ? crlf : lf ;
174}
175
177{
178 const G::Path src = epath( m_state ) ;
179 const G::Path dst = epath( State::Locked ) ;
180 bool ok = false ;
181 {
182 FileWriter claim_writer ;
183 ok = G::File::rename( src , dst , std::nothrow ) ;
184 }
185 if( ok )
186 {
187 G_LOG( "GSmtp::StoredMessage: locking file \"" << src.basename() << "\"" ) ;
188 m_state = State::Locked ;
189 }
190 m_store.updated() ;
191 return ok ;
192}
193
195{
196 G_ASSERT( !rejectees.empty() ) ;
197
198 GSmtp::Envelope env_copy( m_env ) ;
199 env_copy.m_to_remote = rejectees ;
200
201 const G::Path path_in = epath( m_state ) ;
202 const G::Path path_out = epath(m_state).str().append(".tmp") ;
203
204 // create new file
205 std::ofstream out ;
206 {
207 FileWriter claim_writer ;
208 G::File::open( out , path_out ) ;
209 }
210 if( !out.good() )
211 throw EditError( path_in.str() ) ;
212 G::ScopeExit file_deleter( [=](){G::File::remove(path_out,std::nothrow);} ) ;
213
214 // write new file
215 std::size_t endpos = GSmtp::Envelope::write( out , env_copy ) ;
216 if( endpos == 0U )
217 throw EditError( path_in.str() ) ;
218
219 // open existing file
220 std::ifstream in ;
221 {
222 FileReader claim_reader ;
223 G::File::open( in , path_in ) ;
224 }
225 if( !in.good() )
226 throw EditError( path_in.str() ) ;
227
228 // re-read the existing file's endpos, just in case
229 GSmtp::Envelope env_check ;
230 GSmtp::Envelope::read( in , env_check ) ;
231 if( env_check.m_endpos != m_env.m_endpos )
232 G_WARNING( "GSmtp::StoredFile::edit: unexpected change to envelope file detected: " << path_in ) ;
233
234 // copy the existing file's tail to the new file
235 in.seekg( env_check.m_endpos ) ; // NOLINT narrowing
236 if( !in.good() )
237 throw EditError( path_in.str() ) ;
238 GSmtp::Envelope::copy( in , out ) ;
239
240 in.close() ;
241 out.close() ;
242 if( out.fail() )
243 throw EditError( path_in.str() ) ;
244
245 // commit the file
246 bool ok = false ;
247 {
248 FileWriter claim_writer ;
249 ok = G::File::rename( path_out , path_in , std::nothrow ) ;
250 }
251 if( !ok )
252 throw EditError( path_in.str() ) ;
253 file_deleter.release() ;
254
255 m_env.m_crlf = true ;
256 m_env.m_endpos = endpos ;
257 m_env.m_to_remote = rejectees ;
258}
259
260void GSmtp::StoredFile::fail( const std::string & reason , int reason_code )
261{
262 bool exists = false ;
263 {
264 FileReader claim_reader ;
265 exists = G::File::exists( epath(m_state) ) ;
266 }
267 if( exists ) // client-side preprocessing may have removed it
268 {
269 addReason( epath(m_state) , reason , reason_code ) ;
270
271 G::Path bad_path = epath( State::Bad ) ;
272 G_LOG_S( "GSmtp::StoredMessage: failing file: "
273 << "\"" << epath(m_state).basename() << "\" -> "
274 << "\"" << bad_path.basename() << "\"" ) ;
275
276 FileWriter claim_writer ;
277 G::File::rename( epath(m_state) , bad_path , std::nothrow ) ;
278 m_state = State::Bad ;
279 }
280}
281
282void GSmtp::StoredFile::unfail()
283{
284 G_DEBUG( "GSmtp::StoredMessage: unfailing file: " << epath(m_state) ) ;
285 if( m_state == State::Bad )
286 {
287 const G::Path src = epath( m_state ) ;
288 const G::Path dst = epath( State::Normal ) ;
289 bool ok = false ;
290 {
291 FileWriter claim_writer ;
292 ok = G::File::rename( src , dst , std::nothrow ) ;
293 }
294 if( ok )
295 {
296 G_LOG( "GSmtp::StoredMessage: unfailed file: "
297 << "\"" << src.basename() << "\" -> "
298 << "\"" << dst.basename() << "\"" ) ;
299 m_state = State::Normal ;
300 }
301 else
302 {
303 G_WARNING( "GSmtp::StoredMessage: failed to unfail file: \"" << src << "\"" ) ;
304 }
305 }
306}
307
308void GSmtp::StoredFile::addReason( const G::Path & path , const std::string & reason , int reason_code ) const
309{
310 std::ofstream file ;
311 {
312 FileWriter claim_writer ;
313 G::File::open( file , path , G::File::Append() ) ;
314 }
315 if( !file.is_open() )
316 G_ERROR( "GSmtp::StoredFile::addReason: cannot re-open envelope file to append the failure reason: " << path ) ;
317 file << FileStore::x() << "Reason: " << G::Str::toPrintableAscii(reason) << eol() ;
318 file << FileStore::x() << "ReasonCode:" ; if( reason_code ) file << " " << reason_code ; file << eol() ;
319}
320
321void GSmtp::StoredFile::destroy()
322{
323 G_LOG( "GSmtp::StoredMessage: deleting file: \"" << epath(m_state).basename() << "\"" ) ;
324 {
325 FileWriter claim_writer ;
326 G::File::remove( epath(m_state) , std::nothrow ) ;
327 }
328
329 G_LOG( "GSmtp::StoredMessage: deleting file: \"" << cpath().basename() << "\"" ) ;
330 m_content.reset() ; // close it before deleting
331 {
332 FileWriter claim_writer ;
333 G::File::remove( cpath() , std::nothrow ) ;
334 }
335}
336
337std::string GSmtp::StoredFile::from() const
338{
339 return m_env.m_from ;
340}
341
342std::string GSmtp::StoredFile::to( std::size_t i ) const
343{
344 return i < m_env.m_to_remote.size() ? m_env.m_to_remote[i] : std::string() ;
345}
346
347std::size_t GSmtp::StoredFile::toCount() const
348{
349 return m_env.m_to_remote.size() ;
350}
351
352std::istream & GSmtp::StoredFile::contentStream()
353{
354 G_ASSERT( m_content != nullptr ) ;
355 if( m_content == nullptr )
356 m_content = std::make_unique<std::ifstream>() ; // up-cast
357
358 return *m_content ;
359}
360
361std::string GSmtp::StoredFile::authentication() const
362{
363 return m_env.m_authentication ;
364}
365
366std::string GSmtp::StoredFile::fromAuthIn() const
367{
368 return m_env.m_from_auth_in ;
369}
370
371std::string GSmtp::StoredFile::fromAuthOut() const
372{
373 return m_env.m_from_auth_out ;
374}
375
A structure containing the contents of an envelope file, with support for file reading,...
Definition: genvelope.h:39
static void read(std::istream &, Envelope &)
Reads an envelope from a stream.
Definition: genvelope.cpp:102
static std::size_t write(std::ostream &, const Envelope &)
Writes an envelope to a stream.
Definition: genvelope.cpp:50
static void copy(std::istream &, std::ostream &)
A convenience function to copy lines from an input stream to an output stream.
Definition: genvelope.cpp:84
Used by GSmtp::FileStore, GSmtp::NewFile and GSmtp::StoredFile to claim read permissions for reading ...
Definition: gfilestore.h:157
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:58
static std::string x()
Returns the prefix for envelope header lines.
Definition: gfilestore.cpp:129
Used by GSmtp::FileStore, GSmtp::NewFile and GSmtp::StoredFile to claim write permissions.
Definition: gfilestore.h:201
A somewhat opaque identifer for a MessageStore message.
Definition: gmessagestore.h:43
std::string str() const
Returns the id string.
StoredFile(FileStore &store, const G::Path &envelope_path)
Constructor.
Definition: gstoredfile.cpp:31
void fail(const std::string &reason, int reason_code) override
Override from GSmtp::StoredMessage.
bool lock()
Locks the file by renaming the envelope file.
MessageId id() const override
Override from GSmtp::StoredMessage.
Definition: gstoredfile.cpp:66
~StoredFile() override
Destructor.
Definition: gstoredfile.cpp:50
void edit(const G::StringArray &) override
Override from GSmtp::StoredMessage.
bool openContent(std::string &reason)
Opens the content file.
bool readEnvelope(std::string &reason, bool check_for_no_remote_recipients)
Reads the envelope.
An overload discriminator for G::File::open().
Definition: gfile.h:60
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:55
static bool rename(const Path &from, const Path &to, std::nothrow_t) noexcept
Renames the file.
Definition: gfile.cpp:46
static bool remove(const Path &path, std::nothrow_t) noexcept
Deletes the file or directory. Returns false on error.
Definition: gfile.cpp:29
static bool exists(const Path &file)
Returns true if the file (directory, device etc.) exists.
Definition: gfile.cpp:151
A Path object represents a file system path.
Definition: gpath.h:72
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:328
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:346
std::string str() const
Returns the path string.
Definition: gpath.h:215
A class that calls an exit function at the end of its scope.
Definition: gscope.h:46
static std::string toPrintableAscii(const std::string &in, char escape='\\')
Returns a 7-bit printable representation of the given input string.
Definition: gstr.cpp:905
static bool tailMatch(const std::string &in, const std::string &ending)
Returns true if the string has the given ending (or the given ending is empty).
Definition: gstr.cpp:1302
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31