E-MailRelay
gfiledelivery.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 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 G::Path mbox_dir = delivery_dir + mailbox ;
74 if( !FileOp::isdir(mbox_dir) )
75 {
76 G_LOG( "GStore::FileDelivery::deliverToMailboxes: delivery: creating mailbox [" << mailbox << "]" ) ;
77 if( !FileOp::mkdir( mbox_dir ) )
78 throw MkdirError( mbox_dir.str() , G::Process::strerror(FileOp::errno_()) ) ;
79 }
80
81 // copy files
82 deliverTo( m_store , "deliver" , mbox_dir , envelope_path , content_path , m_config.hardlink ) ;
83 }
84
85 // delete the original files if no remote recipients
86 if( envelope.to_remote.empty() && !m_config.no_delete )
87 {
88 FileOp::remove( content_path ) ;
89 FileOp::remove( envelope_path ) ;
90 return true ;
91 }
92 else
93 {
94 return false ;
95 }
96}
97
99 const G::Path & dst_dir , const G::Path & envelope_path , const G::Path & content_path ,
100 bool hardlink , bool pop_by_name )
101{
102 if( FileOp::isdir( dst_dir+"tmp" , dst_dir+"cur" , dst_dir+"new" ) )
103 {
104 // copy content to maildir's "new" sub-directory via "tmp"
105 static int seq {} ;
106 std::ostringstream ss ;
107 ss << G::SystemTime::now() << "." << G::Process::Id().str() << "." << G::hostname() << "." << seq++ ;
108 G::Path tmp_content_path = dst_dir + "tmp" + ss.str() ;
109 G::Path new_content_path = dst_dir + "new" + ss.str() ;
110 if( !FileOp::copy( content_path , tmp_content_path , hardlink ) )
111 throw MaildirCopyError( prefix , tmp_content_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
112 if( !FileOp::rename( tmp_content_path , new_content_path ) )
113 throw MaildirMoveError( prefix , new_content_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
114 G_DEBUG( "GStore::FileDelivery::deliverTo: delivery: delivered " << id(envelope_path) << " as maildir " << ss.str() ) ;
115 }
116 else if( pop_by_name )
117 {
118 // envelope only
119 std::string new_filename = content_path.withoutExtension().basename() ;
120 G::Path new_envelope_path = dst_dir + (new_filename+".envelope") ;
121 if( !FileOp::copy( envelope_path , new_envelope_path ) )
122 throw EnvelopeWriteError( prefix , new_envelope_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
123 }
124 else
125 {
126 std::string new_filename = content_path.withoutExtension().basename() ;
127 G::Path new_content_path = dst_dir + (new_filename+".content") ;
128 G::Path new_envelope_path = dst_dir + (new_filename+".envelope") ;
129 G::ScopeExit clean_up_content( [new_content_path](){FileOp::remove(new_content_path);} ) ;
130
131 // copy or link the content -- maybe edit to add "Delivered-To" etc?
132 bool ok = FileOp::copy( content_path , new_content_path , hardlink ) ;
133 if( !ok )
134 throw ContentWriteError( prefix , new_content_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
135
136 // copy the envelope -- maybe remove other recipients, but no need
137 if( !FileOp::copy( envelope_path , new_envelope_path ) )
138 throw EnvelopeWriteError( prefix , new_envelope_path.str() , G::Process::strerror(FileOp::errno_()) ) ;
139
140 clean_up_content.release() ;
141 G_DEBUG( "GStore::FileDelivery::deliver: " << prefix << ": delivered " << id(envelope_path) << " to mailbox " << dst_dir.basename() ) ;
142 }
143}
144
145G::StringArray GStore::FileDelivery::mailboxes( const Config & config , const GStore::Envelope & envelope )
146{
147 G_ASSERT( !envelope.to_local.empty() ) ;
148 using namespace std::placeholders ;
149 G::StringArray list ;
150 std::transform( envelope.to_local.begin() , envelope.to_local.end() ,
151 std::back_inserter(list) , std::bind(&FileDelivery::mailbox,config,_1) ) ;
152 std::sort( list.begin() , list.end() ) ;
153 list.erase( std::unique( list.begin() , list.end() ) , list.end() ) ;
154 G_ASSERT( !list.empty() ) ;
155 return list ;
156}
157
158std::string GStore::FileDelivery::mailbox( const Config & , const std::string & recipient )
159{
160 // we are only delivering for local recipients where the address verifier
161 // has already mapped the recipient address to a nice mailbox name,
162 // so this is a no-op
163 //
164 const std::string & mailbox = recipient ;
165
166 G_LOG( "GStore::FileDelivery::mailbox: delivery: recipient [" << recipient << "]: delivery to mailbox [" << mailbox << "]" ) ;
167 return mailbox ;
168}
169
170std::string GStore::FileDelivery::id( const G::Path & envelope_path )
171{
172 return envelope_path.withoutExtension().basename() ;
173}
174
175G::Path GStore::FileDelivery::epath( const MessageId & message_id , FileStore::State store_state ) const
176{
177 return m_store.envelopePath( message_id , store_state ) ;
178}
179
180G::Path GStore::FileDelivery::cpath( const MessageId & message_id ) const
181{
182 return m_store.contentPath( message_id ) ;
183}
184
A structure containing the contents of an envelope file, with support for file reading,...
Definition: genvelope.h:41
FileDelivery(FileStore &, const Config &)
Constructor.
static void deliverTo(FileStore &, G::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...
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:73
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:346
Path withoutExtension() const
Returns a path without the basename extension, if any.
Definition: gpath.cpp:364
std::string str() const
Returns the path string.
Definition: gpath.h:224
Process-id class.
Definition: gprocess.h:155
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:46
static SystemTime now()
Factory function for the current time.
Definition: gdatetime.cpp:328
A class like c++17's std::string_view.
Definition: gstringview.h:51
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