E-MailRelay

gnewfile.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 gnewfile.cpp
19///
20
21#include "gdef.h"
22#include "gfilestore.h"
23#include "gnewfile.h"
24#include "gprocess.h"
25#include "groot.h"
26#include "gfile.h"
27#include "gstr.h"
28#include "gxtext.h"
29#include "geightbit.h"
30#include "gassert.h"
31#include "glog.h"
32#include <functional>
33#include <algorithm>
34#include <iostream>
35#include <fstream>
36
37 GSmtp::NewFile::NewFile( FileStore & store , const std::string & from ,
38 const std::string & from_auth_in , const std::string & from_auth_out ,
39 std::size_t max_size , bool test_for_eight_bit ) :
40 m_store(store) ,
41 m_id(store.newId()) ,
42 m_committed(false) ,
43 m_test_for_eight_bit(test_for_eight_bit) ,
44 m_saved(false) ,
45 m_size(0UL) ,
46 m_max_size(max_size)
47{
48 m_env.m_from = from ;
49 m_env.m_from_auth_in = from_auth_in ;
50 m_env.m_from_auth_out = from_auth_out ;
51
52 // ask the store for a content stream
53 G_LOG( "GSmtp::NewMessage: content file: " << cpath() ) ;
54 m_content = m_store.stream( cpath() ) ;
55}
56
57 GSmtp::NewFile::~NewFile()
58{
59 try
60 {
61 G_DEBUG( "GSmtp::NewFile::dtor: " << cpath() ) ;
62 cleanup() ;
63 }
64 catch(...) // dtor
65 {
66 }
67}
68
69 void GSmtp::NewFile::cleanup()
70{
71 discardContent() ;
72 if( ! m_committed )
73 {
74 deleteEnvelope() ;
75 deleteContent() ;
76 }
77}
78
79 bool GSmtp::NewFile::prepare( const std::string & session_auth_id , const std::string & peer_socket_address ,
80 const std::string & peer_certificate )
81{
82 // flush and close the content file
83 G_ASSERT( m_content != nullptr ) ;
84 m_content->close() ;
85 if( m_content->fail() ) // trap failbit/badbit
86 throw FileError( "cannot write content file " + cpath().str() ) ;
87 m_content.reset() ;
88
89 // write the envelope
90 m_env.m_authentication = session_auth_id ;
91 m_env.m_client_socket_address = peer_socket_address ;
92 m_env.m_client_certificate = peer_certificate ;
93 if( !saveEnvelope() )
94 throw FileError( "cannot write envelope file " + epath(State::New).str() ) ;
95
96 // copy or move aside for local mailboxes
97 if( !m_env.m_to_local.empty() && m_env.m_to_remote.empty() )
98 {
99 moveToLocal( cpath() , epath(State::New) , epath(State::Normal) ) ;
100 m_store.updated() ; // (new)
101 return true ; // no commit() needed
102 }
103 else if( !m_env.m_to_local.empty() )
104 {
105 copyToLocal( cpath() , epath(State::New) , epath(State::Normal) ) ;
106 return false ;
107 }
108 else
109 {
110 return false ;
111 }
112}
113
114 void GSmtp::NewFile::commit( bool throw_on_error )
115{
116 m_committed = true ;
117 bool ok = commitEnvelope() ;
118 if( !ok && throw_on_error )
119 throw FileError( "cannot rename envelope file to " + epath(State::Normal).str() ) ;
120 if( ok )
121 m_store.updated() ;
122}
123
124 void GSmtp::NewFile::addTo( const std::string & to , bool local )
125{
126 if( local )
127 m_env.m_to_local.push_back( to ) ;
128 else
129 m_env.m_to_remote.push_back( to ) ;
130}
131
132 bool GSmtp::NewFile::addText( const char * line_data , std::size_t line_size )
133{
134 m_size += static_cast<unsigned long>( line_size ) ;
135
136 // testing for eight-bit content can be relatively slow -- not testing
137 // the content is implied by RFC-2821 ("a relay should ... relay
138 // [messages] without inspecting [the] content"), but RFC-6152
139 // disallows sending eight-bit content to a seven-bit server --
140 // here we optionally do the test on each line of content -- the
141 // final result is written into the envelope file, allowing an
142 // external program to change it before forwarding
143 //
144 if( m_test_for_eight_bit && m_env.m_eight_bit != 1 )
145 m_env.m_eight_bit = isEightBit(line_data,line_size) ? 1 : 0 ;
146
147 std::ostream & stream = *m_content ;
148 stream.write( line_data , line_size ) ; // NOLINT narrowing
149
150 return m_max_size == 0UL || m_size < m_max_size ;
151}
152
153 void GSmtp::NewFile::discardContent()
154{
155 m_content.reset() ;
156}
157
158 void GSmtp::NewFile::deleteContent()
159{
160 FileWriter claim_writer ;
161 G::File::remove( cpath() , std::nothrow ) ;
162}
163
164 void GSmtp::NewFile::deleteEnvelope()
165{
166 FileWriter claim_writer ;
167 G::File::remove( epath(State::New) , std::nothrow ) ;
168}
169
170 bool GSmtp::NewFile::isEightBit( const char * line_data , std::size_t line_size )
171{
172 return G::eightbit( line_data , line_size ) ;
173}
174
175 bool GSmtp::NewFile::saveEnvelope()
176{
177 G_LOG( "GSmtp::NewMessage: envelope file: " << epath(State::New).basename() ) ; // (was full path)
178 std::unique_ptr<std::ofstream> envelope_stream = m_store.stream( epath(State::New) ) ;
179 m_env.m_endpos = GSmtp::Envelope::write( *envelope_stream , m_env ) ;
180 m_env.m_crlf = true ;
181 envelope_stream->close() ;
182 return !envelope_stream->fail() ;
183}
184
185 bool GSmtp::NewFile::commitEnvelope()
186{
187 FileWriter claim_writer ;
188 m_saved = G::File::rename( epath(State::New) , epath(State::Normal) , std::nothrow ) ;
189 return m_saved ;
190}
191
192 void GSmtp::NewFile::moveToLocal( const G::Path & content_path , const G::Path & envelope_path_now ,
193 const G::Path & envelope_path_later )
194{
195 G_LOG_S( "GSmtp::NewMessage: message for local-mailbox recipient(s): " << content_path.basename() << ".local" ) ;
196 FileWriter claim_writer ;
197 G::File::rename( content_path.str() , content_path.str()+".local" ) ;
198 G::File::rename( envelope_path_now.str() , envelope_path_later.str()+".local" ) ;
199}
200
201 void GSmtp::NewFile::copyToLocal( const G::Path & content_path , const G::Path & envelope_path_now ,
202 const G::Path & envelope_path_later )
203{
204 G_LOG_S( "GSmtp::NewMessage: message for local-mailbox recipient(s): " << content_path.basename() << ".local" ) ;
205 FileWriter claim_writer ;
206 G::File::copy( content_path.str() , content_path.str()+".local" ) ;
207 G::File::copy( envelope_path_now.str() , envelope_path_later.str()+".local" ) ;
208}
209
210 GSmtp::MessageId GSmtp::NewFile::id() const
211{
212 return m_id ;
213}
214
215std::string GSmtp::NewFile::location() const
216{
217 return cpath().str() ;
218}
219
220 G::Path GSmtp::NewFile::cpath() const
221{
222 return m_store.contentPath( m_id ) ;
223}
224
225 G::Path GSmtp::NewFile::epath( State state ) const
226{
227 return state == State::Normal ?
228 m_store.envelopePath(m_id) :
229 G::Path( m_store.envelopePath(m_id).str() + ".new" ) ;
230}
231
GSmtp::Envelope::write
static std::size_t write(std::ostream &, const Envelope &)
Writes an envelope to a stream.
Definition: genvelope.cpp:50
GSmtp::FileStore
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:58
GSmtp::FileStore::stream
std::unique_ptr< std::ofstream > stream(const G::Path &path)
Returns a stream to the given content.
Definition: gfilestore.cpp:190
GSmtp::MessageId
A somewhat opaque identifer for a MessageStore message.
Definition: gmessagestore.h:43
GSmtp::NewFile::~NewFile
~NewFile() override
Destructor.
Definition: gnewfile.cpp:57
GSmtp::NewFile::NewFile
NewFile(FileStore &store, const std::string &from, const std::string &from_auth_in, const std::string &from_auth_out, std::size_t max_size, bool test_for_eight_bit)
Constructor. The FileStore reference is kept.
Definition: gnewfile.cpp:37
G::File::rename
static bool rename(const Path &from, const Path &to, std::nothrow_t) noexcept
Renames the file.
Definition: gfile.cpp:46
G::File::remove
static bool remove(const Path &path, std::nothrow_t) noexcept
Deletes the file or directory. Returns false on error.
Definition: gfile.cpp:29
G::File::copy
static bool copy(const Path &from, const Path &to, std::nothrow_t)
Copies a file. Returns false on error.
Definition: gfile.cpp:83
G::Path
A Path object represents a file system path.
Definition: gpath.h:72
G::Path::basename
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:328
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.h:215
gassert.h
geightbit.h
gfile.h
gfilestore.h
glog.h
gnewfile.h
gprocess.h
groot.h
gstr.h
gxtext.h
G::eightbit
bool eightbit(const unsigned char *p, std::size_t n)
Returns true if the given data buffer contains a byte greater than 127.
Definition: geightbit.h:108