E-MailRelay
gfiledelivery.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 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 gfiledelivery.cpp
19///
20
21#include "gdef.h"
22#include "gfiledelivery.h"
23#include "gfilestore.h"
24#include "grange.h"
25#include "gfile.h"
26#include "gscope.h"
27#include "gstringarray.h"
28#include "gprocess.h"
29#include "ghostname.h"
30#include "gexception.h"
31#include "gassert.h"
32#include "glog.h"
33#include <algorithm>
34#include <sstream>
35
37 m_store(store) ,
38 m_config(config)
39{
40}
41
42bool GStore::FileDelivery::deliver( const MessageId & message_id , bool is_new )
43{
44 bool deleted = false ;
45 FileStore::State store_state = is_new ? FileStore::State::New : FileStore::State::Locked ;
46 G::Path envelope_path = epath( message_id , store_state ) ;
47 Envelope envelope = FileStore::readEnvelope( envelope_path ) ;
48 if( !envelope.to_local.empty() )
49 {
50 if( m_store.directory() != m_store.deliveryDir() )
51 {
52 G_LOG( "GStore::FileDelivery::deliver: delivery: delivering " << message_id.str() << " to "
53 "[" << m_store.deliveryDir() << "/<mbox>]" ) ;
54 }
55
56 deleted = deliverToMailboxes( m_store.deliveryDir() , envelope , envelope_path , cpath(message_id) ) ;
57 }
58 return deleted ;
59}
60
61bool GStore::FileDelivery::deliverToMailboxes( const G::Path & delivery_dir , const Envelope & envelope ,
62 const G::Path & envelope_path , const G::Path & content_path )
63{
64 G_ASSERT( !envelope.to_local.empty() ) ;
65
66 // map recipient addresses to mailbox names
67 G::StringArray mailbox_list = mailboxes( m_config , envelope ) ;
68
69 // process each mailbox
70 for( const auto & mailbox : mailbox_list )
71 {
72 // create the target directory if necessary
73 // (see also GPop::Store::prepare())
74 //
75 if( mailbox.empty() || !G::Str::isPrintable(mailbox) || !G::Path(mailbox).simple() )
76 throw MkdirError( "invalid mailbox name" , G::Str::printable(mailbox) ) ;
77 G::Path mbox_dir = delivery_dir/mailbox ;
78 if( !FileOp::isdir(mbox_dir) )
79 {
80 G_LOG( "GStore::FileDelivery::deliverToMailboxes: delivery: creating mailbox [" << mailbox << "]" ) ;
81 if( !FileOp::mkdir( mbox_dir ) )
82 throw MkdirError( mbox_dir.str() , G::Process::strerror(FileOp::errno_()) ) ;
83 }
84
85 // copy files
86 deliverTo( m_store , "deliver" , mbox_dir , envelope_path , content_path , m_config.hardlink ) ;
87 }
88
89 // delete the original files if no remote recipients
90 if( envelope.to_remote.empty() && !m_config.no_delete )
91 {
92 FileOp::remove( content_path ) ;
93 FileOp::remove( envelope_path ) ;
94 return true ;
95 }
96 else
97 {
98 return false ;
99 }
100}
101
102void GStore::FileDelivery::deliverTo( FileStore & /*store*/ , std::string_view prefix ,
103 const G::Path & dst_dir , const G::Path & envelope_path , const G::Path & content_path ,
104 bool hardlink , bool pop_by_name )
105{
106 if( FileOp::isdir( dst_dir/"tmp" , dst_dir/"cur" , dst_dir/"new" ) )
107 {
108 // copy content to maildir's "new" sub-directory via "tmp"
109 static int seq {} ;
110 std::ostringstream ss ;
111 ss << G::SystemTime::now() << "." << G::Process::Id().str() << "." << hostname() << "." << seq++ ;
112 G::Path tmp_content_path = dst_dir/"tmp"/ss.str() ;
113 G::Path new_content_path = dst_dir/"new"/ss.str() ;
114 if( !FileOp::copy( content_path , tmp_content_path , hardlink ) )
115 throw MaildirCopyError( prefix , tmp_content_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
116 if( !FileOp::rename( tmp_content_path , new_content_path ) )
117 throw MaildirMoveError( prefix , new_content_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
118 G_DEBUG( "GStore::FileDelivery::deliverTo: delivery: delivered " << id(envelope_path) << " as maildir " << ss.str() ) ;
119 }
120 else if( pop_by_name )
121 {
122 // envelope only
123 std::string new_filename = content_path.withoutExtension().basename() ;
124 G::Path new_envelope_path = dst_dir / (new_filename+".envelope") ;
125 if( !FileOp::copy( envelope_path , new_envelope_path ) )
126 throw EnvelopeWriteError( prefix , new_envelope_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
127 }
128 else
129 {
130 std::string new_filename = content_path.withoutExtension().basename() ;
131 G::Path new_content_path = dst_dir / (new_filename+".content") ;
132 G::Path new_envelope_path = dst_dir / (new_filename+".envelope") ;
133 G::ScopeExit clean_up_content( [new_content_path](){FileOp::remove(new_content_path);} ) ;
134
135 // copy or link the content -- maybe edit to add "Delivered-To" etc?
136 bool ok = FileOp::copy( content_path , new_content_path , hardlink ) ;
137 if( !ok )
138 throw ContentWriteError( prefix , new_content_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
139
140 // copy the envelope -- maybe remove other recipients, but no need
141 if( !FileOp::copy( envelope_path , new_envelope_path ) )
142 throw EnvelopeWriteError( prefix , new_envelope_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
143
144 clean_up_content.release() ;
145 G_DEBUG( "GStore::FileDelivery::deliver: " << prefix << ": delivered " << id(envelope_path) << " to mailbox " << dst_dir.basename() ) ;
146 }
147}
148
149G::StringArray GStore::FileDelivery::mailboxes( const Config & config , const GStore::Envelope & envelope )
150{
151 G_ASSERT( !envelope.to_local.empty() ) ;
152 using namespace std::placeholders ;
153 G::StringArray list ;
154 std::transform( envelope.to_local.begin() , envelope.to_local.end() ,
155 std::back_inserter(list) , std::bind(&FileDelivery::mailbox,config,_1) ) ;
156 std::sort( list.begin() , list.end() ) ;
157 list.erase( std::unique( list.begin() , list.end() ) , list.end() ) ;
158 G_ASSERT( !list.empty() ) ;
159 return list ;
160}
161
162std::string GStore::FileDelivery::mailbox( const Config & , const std::string & recipient )
163{
164 // we are only delivering for local recipients where the address verifier
165 // has already mapped the recipient address to a nice mailbox name,
166 // so this is a no-op
167 //
168 const std::string & mailbox = recipient ;
169
170 G_LOG( "GStore::FileDelivery::mailbox: delivery: recipient [" << recipient << "]: delivery to mailbox [" << mailbox << "]" ) ;
171 return mailbox ;
172}
173
174std::string GStore::FileDelivery::id( const G::Path & envelope_path )
175{
176 return envelope_path.withoutExtension().basename() ;
177}
178
179G::Path GStore::FileDelivery::epath( const MessageId & message_id , FileStore::State store_state ) const
180{
181 return m_store.envelopePath( message_id , store_state ) ;
182}
183
184G::Path GStore::FileDelivery::cpath( const MessageId & message_id ) const
185{
186 return m_store.contentPath( message_id ) ;
187}
188
189std::string GStore::FileDelivery::hostname()
190{
191 std::string name = G::hostname() ;
192 if( name.empty() ) name = "localhost" ;
193 G::Str::replace( name , '/' , '_' ) ;
194 G::Str::replace( name , '\\' , '_' ) ;
195 G::Str::replace( name , '.' , '_' ) ;
196 return name ;
197}
198
A structure containing the contents of an envelope file, with support for file reading,...
Definition: genvelope.h:41
static void deliverTo(FileStore &, std::string_view prefix, const G::Path &dst_dir, const G::Path &envelope_path, const G::Path &content_path, bool hardlink=false, bool pop_by_name=false)
Low-level function to copy a single message into a mailbox sub-directory or a pop-by-name sub-directo...
FileDelivery(FileStore &, const Config &)
Constructor.
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:56
static Envelope readEnvelope(const G::Path &, std::ifstream *=nullptr)
Used by FileStore sibling classes to read an envelope file.
Definition: gfilestore.cpp:308
A somewhat opaque identifer for a GStore::MessageStore message id.
Definition: gmessagestore.h:43
std::string str() const
Returns the id string.
A Path object represents a file system path.
Definition: gpath.h:82
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:339
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:357
bool simple() const
Returns true if the path has a single component (ignoring "." parts), ie.
Definition: gpath.cpp:319
std::string str() const
Returns the path string.
Definition: gpath.h:243
Process-id class.
Definition: gprocess.h:167
static std::string strerror(int errno_)
Translates an 'errno' value into a meaningful diagnostic string.
A class that calls an exit function at the end of its scope.
Definition: gscope.h:47
static bool isPrintable(std::string_view s) noexcept
Returns true if every character is 0x20 or above but not 0x7f.
Definition: gstr.cpp:418
static std::string printable(const std::string &in, char escape='\\')
Returns a printable representation of the given input string, using chacter code ranges 0x20 to 0x7e ...
Definition: gstr.cpp:913
static bool replace(std::string &s, std::string_view from, std::string_view to, std::size_t *pos_p=nullptr)
A std::string_view overload.
Definition: gstr.cpp:226
static SystemTime now()
Factory function for the current time.
Definition: gdatetime.cpp:328
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
std::string hostname()
Returns the hostname.
A configuration structure for GStore::FileDelivery.
Definition: gfiledelivery.h:58