E-MailRelay
gfilestore.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 gfilestore.cpp
19///
20
21#include "gdef.h"
22#include "gfilestore.h"
23#include "gnewfile.h"
24#include "gstoredfile.h"
25#include "gprocess.h"
26#include "gdirectory.h"
27#include "gformat.h"
28#include "ggettext.h"
29#include "groot.h"
30#include "gpath.h"
31#include "gfile.h"
32#include "gstr.h"
33#include "gassert.h"
34#include "gtest.h"
35#include "glog.h"
36#include <iostream>
37#include <fstream>
38
39namespace GSmtp
40{
41 class FileIterator ;
42}
43
44class GSmtp::FileIterator : public MessageStore::Iterator /// A GSmtp::MessageStore::Iterator for GSmtp::FileStore.
45{
46public:
47 FileIterator( FileStore & store , const G::Path & dir , bool lock , bool failures ) ;
48 ~FileIterator() override ;
49
50private: // overrides
51 std::unique_ptr<StoredMessage> next() override ;
52
53public:
54 FileIterator( const FileIterator & ) = delete ;
55 FileIterator( FileIterator && ) = delete ;
56 void operator=( const FileIterator & ) = delete ;
57 void operator=( FileIterator && ) = delete ;
58
59private:
60 FileStore & m_store ;
61 G::DirectoryList m_iter ;
62 bool m_lock ;
63} ;
64
65// ===
66
67GSmtp::FileIterator::FileIterator( FileStore & store , const G::Path & dir , bool lock , bool failures ) :
68 m_store(store) ,
69 m_lock(lock)
70{
71 DirectoryReader claim_reader ;
72 m_iter.readType( dir , std::string(failures?".envelope.bad":".envelope") ) ;
73}
74
75GSmtp::FileIterator::~FileIterator()
76= default;
77
78std::unique_ptr<GSmtp::StoredMessage> GSmtp::FileIterator::next()
79{
80 while( m_iter.more() )
81 {
82 auto m = std::make_unique<StoredFile>( m_store , m_iter.filePath() ) ;
83 if( !m->id().valid() )
84 continue ;
85
86 if( m_lock && !m->lock() )
87 {
88 G_WARNING( "GSmtp::MessageStore: cannot lock file: \"" << m_iter.filePath() << "\"" ) ;
89 continue ;
90 }
91
92 std::string reason ;
93 const bool check_recipients = m_lock ; // check for no-remote-recipients
94 bool ok = m->readEnvelope(reason,check_recipients) && m->openContent(reason) ;
95 if( !ok && m_lock )
96 m->fail( reason , 0 ) ;
97 else if( !ok )
98 G_WARNING( "GSmtp::MessageStore: ignoring \"" << m_iter.filePath() << "\": " << reason ) ;
99 else
100 return std::unique_ptr<StoredMessage>( m.release() ) ; // up-cast
101 }
102 return {} ;
103}
104
105// ===
106
107GSmtp::FileStore::FileStore( const G::Path & dir , unsigned long max_size , bool test_for_eight_bit ) :
108 m_seq(1UL) ,
109 m_dir(dir) ,
110 m_max_size(max_size) ,
111 m_test_for_eight_bit(test_for_eight_bit)
112{
113 m_pid_modifier = static_cast<unsigned long>(G::SystemTime::now().s()) ;
114 checkPath( dir ) ;
115
116 if( G::Test::enabled("message-store-with-8bit-test") )
117 m_test_for_eight_bit = true ;
118
119 if( G::Test::enabled("message-store-without-8bit-test") )
120 m_test_for_eight_bit = false ;
121
122 if( G::Test::enabled("message-store-unfail") )
123 unfailAllImp() ;
124
125 if( G::Test::enabled("message-store-clear") )
126 clearAll() ;
127}
128
130{
131 return "X-MailRelay-" ;
132}
133
134std::string GSmtp::FileStore::format( int generation )
135{
136 // use a weird prefix to help with file(1) and magic(5)
137 if( generation == -2 )
138 return "#2821.3" ; // original
139 else if( generation == -1 )
140 return "#2821.4" ; // new for 1.9
141 else
142 return "#2821.5" ; // new for 2.0
143}
144
145bool GSmtp::FileStore::knownFormat( const std::string & format_in )
146{
147 return
148 format_in == format(0) ||
149 format_in == format(-1) ||
150 format_in == format(-2) ;
151}
152
153void GSmtp::FileStore::checkPath( const G::Path & directory_path )
154{
155 G::Directory dir_test( directory_path ) ;
156 bool ok = false ;
157 int error = 0 ;
158 std::string reason ;
159
160 // fail if not readable (after switching effective userid)
161 {
162 FileWriter claim_writer ;
163 error = dir_test.usable() ;
164 ok = error == 0 ;
165 }
166 if( !ok )
167 {
168 throw InvalidDirectory( directory_path.str() , G::Process::strerror(error) ) ;
169 }
170
171 // warn if not writeable (after switching effective userid)
172 {
173 std::string tmp_filename = G::Directory::tmp() ;
174 FileWriter claim_writer ;
175 ok = dir_test.writeable( tmp_filename ) ;
176 }
177 if( !ok )
178 {
179 using G::format ;
180 using G::gettext ;
181 G_WARNING( "GSmtp::MessageStore: " << format(gettext("directory not writable: \"%1%\"")) % directory_path ) ;
182 }
183}
184
185std::string GSmtp::FileStore::location( const MessageId & id ) const
186{
187 return envelopePath(id).str() ;
188}
189
190std::unique_ptr<std::ofstream> GSmtp::FileStore::stream( const G::Path & path )
191{
192 auto stream_ptr = std::make_unique<std::ofstream>() ;
193 {
194 FileWriter claim_writer ; // seteuid(), umask(Tighter)
195 G::File::open( *stream_ptr , path ) ;
196 }
197 return stream_ptr ;
198}
199
201{
202 return envelopePath(id).withExtension( "content" ) ;
203}
204
205G::Path GSmtp::FileStore::envelopePath( const MessageId & id , const char * modifier ) const
206{
207 G_ASSERT( modifier != nullptr ) ;
208 return m_dir + id.str().append(".envelope").append(modifier) ;
209}
210
212{
213 unsigned long timestamp = static_cast<unsigned long>(G::SystemTime::now().s()) ;
214
215 m_seq++ ;
216 if( m_seq == 0UL )
217 m_seq++ ;
218
219 std::ostringstream ss ;
220 ss << "emailrelay." << G::Process::Id().str() << "." << timestamp << "." << m_seq ;
221 return MessageId( ss.str() ) ;
222}
223
225{
226 G::DirectoryList list ;
227 DirectoryReader claim_reader ;
228 list.readType( m_dir , ".envelope" , 1U ) ;
229 const bool no_more = !list.more() ;
230 return no_more ;
231}
232
233std::shared_ptr<GSmtp::MessageStore::Iterator> GSmtp::FileStore::iterator( bool lock )
234{
235 return iteratorImp( lock ) ;
236}
237
238std::shared_ptr<GSmtp::MessageStore::Iterator> GSmtp::FileStore::iteratorImp( bool lock )
239{
240 return std::make_shared<FileIterator>( *this , m_dir , lock , false ) ; // up-cast
241}
242
243std::shared_ptr<GSmtp::MessageStore::Iterator> GSmtp::FileStore::failures()
244{
245 return std::make_shared<FileIterator>( *this , m_dir , false , true ) ; // up-cast
246}
247
248std::unique_ptr<GSmtp::StoredMessage> GSmtp::FileStore::get( const MessageId & id )
249{
250 G::Path path = envelopePath( id ) ;
251
252 auto message = std::make_unique<StoredFile>( *this , path ) ;
253 if( !message->lock() )
254 throw GetError( path.str() + ": cannot lock the file" ) ;
255
256 std::string reason ;
257 const bool check_recipients = false ; // don't check for no-remote-recipients
258 if( !message->readEnvelope(reason,check_recipients) )
259 throw GetError( path.str() + ": cannot read the envelope: " + reason ) ;
260
261 if( !message->openContent(reason) )
262 throw GetError( path.str() + ": cannot read the content: " + reason ) ;
263
264 return std::unique_ptr<StoredMessage>( message.release() ) ; // up-cast
265}
266
267std::unique_ptr<GSmtp::NewMessage> GSmtp::FileStore::newMessage( const std::string & from ,
268 const std::string & from_auth_in , const std::string & from_auth_out )
269{
270 return std::make_unique<NewFile>( *this , from , from_auth_in , from_auth_out ,
271 m_max_size , m_test_for_eight_bit ) ; // up-cast
272}
273
275{
276 G_DEBUG( "GSmtp::FileStore::updated" ) ;
277 m_update_signal.emit() ;
278}
279
281{
282 return m_update_signal ;
283}
284
286{
287 return m_rescan_signal ;
288}
289
290void GSmtp::FileStore::rescan()
291{
292 messageStoreRescanSignal().emit() ;
293}
294
295void GSmtp::FileStore::unfailAll()
296{
297 unfailAllImp() ;
298}
299
300void GSmtp::FileStore::unfailAllImp()
301{
302 std::shared_ptr<MessageStore::Iterator> iter( failures() ) ;
303 for(;;)
304 {
305 std::unique_ptr<StoredMessage> message = iter->next() ;
306 if( message == nullptr )
307 break ;
308 G_DEBUG( "GSmtp::FileStore::unfailAllImp: " << message->location() ) ;
309 message->unfail() ;
310 }
311}
312
313void GSmtp::FileStore::clearAll()
314{
315 // for testing...
316 std::shared_ptr<MessageStore::Iterator> iter( iteratorImp(true) ) ;
317 for(;;)
318 {
319 std::unique_ptr<StoredMessage> message = iter->next() ;
320 if( message )
321 message->destroy() ;
322 else
323 break ;
324 }
325}
326
327// ===
328
330= default;
331
333= default;
334
335// ===
336
338= default;
339
341= default;
342
343// ===
344
346 G::Root(false) ,
347 G::Process::Umask(G::Process::Umask::Mode::Tighter)
348{
349}
350
352= default;
353
Used by GSmtp::FileStore, GSmtp::NewFile and GSmtp::StoredFile to claim read permissions for reading ...
Definition: gfilestore.h:179
~DirectoryReader()
Destructor. Switches identity back.
DirectoryReader()
Default constructor.
A GSmtp::MessageStore::Iterator for GSmtp::FileStore.
Definition: gfilestore.cpp:45
FileReader()
Default constructor.
~FileReader()
Destructor. Switches identity back.
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:58
G::Slot::Signal & messageStoreRescanSignal() override
Override from GSmtp::MessageStore.
Definition: gfilestore.cpp:285
FileStore(const G::Path &dir, unsigned long max_size, bool test_for_eight_bit)
Constructor.
Definition: gfilestore.cpp:107
std::unique_ptr< StoredMessage > get(const MessageId &) override
Override from GSmtp::MessageStore.
Definition: gfilestore.cpp:248
std::shared_ptr< MessageStore::Iterator > failures() override
Override from GSmtp::MessageStore.
Definition: gfilestore.cpp:243
static bool knownFormat(const std::string &format)
Returns true if the storage format string is recognised and supported for reading.
Definition: gfilestore.cpp:145
void updated() override
Override from GSmtp::MessageStore.
Definition: gfilestore.cpp:274
std::unique_ptr< std::ofstream > stream(const G::Path &path)
Returns a stream to the given content.
Definition: gfilestore.cpp:190
G::Path contentPath(const MessageId &) const
Returns the path for a content file.
Definition: gfilestore.cpp:200
std::shared_ptr< MessageStore::Iterator > iterator(bool lock) override
Override from GSmtp::MessageStore.
Definition: gfilestore.cpp:233
std::unique_ptr< NewMessage > newMessage(const std::string &from, const std::string &from_auth_in, const std::string &from_auth_out) override
Override from GSmtp::MessageStore.
Definition: gfilestore.cpp:267
static std::string x()
Returns the prefix for envelope header lines.
Definition: gfilestore.cpp:129
G::Path envelopePath(const MessageId &, const char *modifier="") const
Returns the path for an envelope file.
Definition: gfilestore.cpp:205
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:134
bool empty() const override
Override from GSmtp::MessageStore.
Definition: gfilestore.cpp:224
std::string location(const MessageId &) const override
Override from GSmtp::MessageStore.
Definition: gfilestore.cpp:185
G::Slot::Signal & messageStoreUpdateSignal() override
Override from GSmtp::MessageStore.
Definition: gfilestore.cpp:280
MessageId newId()
Hands out a new message id.
Definition: gfilestore.cpp:211
Used by GSmtp::FileStore, GSmtp::NewFile and GSmtp::StoredFile to claim write permissions.
Definition: gfilestore.h:201
~FileWriter()
Destructor. Switches identity back.
FileWriter()
Default constructor.
Definition: gfilestore.cpp:345
A somewhat opaque identifer for a MessageStore message.
Definition: gmessagestore.h:43
A iterator similar to G::DirectoryIterator but doing all file i/o in one go.
Definition: gdirectory.h:146
void readType(const Path &dir, const std::string &suffix, unsigned int limit=0U)
An initialiser that is to be used after default construction.
Definition: gdirectory.cpp:88
bool more()
Returns true if more and advances by one.
Definition: gdirectory.cpp:112
An encapsulation of a file system directory that works with G::DirectoryIterator.
Definition: gdirectory.h:46
static std::string tmp()
A convenience function for constructing a filename for writeable().
Definition: gdirectory.cpp:57
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:55
A Path object represents a file system path.
Definition: gpath.h:72
Path withExtension(const std::string &ext) const
Returns the path with the new basename extension.
Definition: gpath.cpp:364
std::string str() const
Returns the path string.
Definition: gpath.h:215
Process-id class.
Definition: gprocess.h:140
static std::string strerror(int errno_)
Translates an 'errno' value into a meaningful diagnostic string.
static SystemTime now()
Factory function for the current time.
Definition: gdatetime.cpp:260
std::time_t s() const noexcept
Returns the number of seconds since the start of the epoch.
Definition: gdatetime.cpp:308
static bool enabled() noexcept
Returns true if test features are enabled.
Definition: gtest.cpp:79
A simple version of boost::format for formatting strings in an i18n-friendly way.
Definition: gformat.h:46
SMTP and message-store classes.
Definition: gadminserver.h:39
Low-level classes.
Definition: galign.h:28
const char * gettext(const char *)
Returns the message translation in the current locale's codeset, eg.
Definition: ggettext.h:69
A base class for GSmtp::MessageStore iterators.
Definition: gmessagestore.h:76
A slot holder, with connect() and emit() methods.
Definition: gslot.h:157