E-MailRelay

gexecutablefilter.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 gexecutablefilter.cpp
19///
20
21#include "gdef.h"
22#include "gprocess.h"
23#include "gnewprocess.h"
24#include "gexecutablecommand.h"
25#include "gexecutablefilter.h"
26#include "gstr.h"
27#include "groot.h"
28#include "glog.h"
29#include "gassert.h"
30#include <tuple>
31
32 GSmtp::ExecutableFilter::ExecutableFilter( GNet::ExceptionSink es , FileStore & file_store ,
33 bool server_side , const std::string & path , unsigned int timeout ) :
34 m_file_store(file_store) ,
35 m_server_side(server_side) ,
36 m_prefix(server_side?"filter":"client filter") ,
37 m_exit(0,server_side) ,
38 m_path(path) ,
39 m_timeout(timeout) ,
40 m_timer(*this,&ExecutableFilter::onTimeout,es) ,
41 m_task(*this,es,"<<filter exec error: __strerror__>>",G::Root::nobody())
42{
43}
44
45 GSmtp::ExecutableFilter::~ExecutableFilter()
46= default;
47
48 bool GSmtp::ExecutableFilter::simple() const
49{
50 return false ;
51}
52
53std::string GSmtp::ExecutableFilter::id() const
54{
55 return m_path.str() ;
56}
57
58 bool GSmtp::ExecutableFilter::special() const
59{
60 return m_exit.special ;
61}
62
63std::string GSmtp::ExecutableFilter::response() const
64{
65 G_ASSERT( m_exit.ok() || m_exit.abandon() || !m_response.empty() ) ;
66 if( m_exit.ok() || m_exit.abandon() )
67 return std::string() ;
68 else
69 return m_response ;
70}
71
72std::string GSmtp::ExecutableFilter::reason() const
73{
74 G_ASSERT( m_exit.ok() || m_exit.abandon() || !m_reason.empty() ) ;
75 if( m_exit.ok() || m_exit.abandon() )
76 return std::string() ;
77 else
78 return m_reason ;
79}
80
81 void GSmtp::ExecutableFilter::start( const MessageId & message_id )
82{
83 G::Path cpath = m_file_store.contentPath( message_id ) ;
84 G::Path epath = m_file_store.envelopePath( message_id , m_server_side ? ".new" : ".busy" ) ;
85
86 G::StringArray args ;
87 args.push_back( cpath.str() ) ;
88 args.push_back( epath.str() ) ;
89
90 G::ExecutableCommand commandline( m_path.str() , args ) ;
91 G_LOG( "GSmtp::ExecutableFilter::start: " << m_prefix << ": running " << commandline.displayString() ) ;
92 m_task.start( commandline ) ;
93
94 if( m_timeout )
95 m_timer.startTimer( m_timeout ) ;
96}
97
98 void GSmtp::ExecutableFilter::onTimeout()
99{
100 G_WARNING( "GSmtp::ExecutableFilter::onTimeout: " << m_prefix << " timed out after " << m_timeout << "s" ) ;
101 m_task.stop() ;
102 m_exit = Exit( 1 , m_server_side ) ;
103 G_ASSERT( m_exit.fail() && !special() && !abandoned() ) ;
104 m_response = "error" ;
105 m_reason = "timeout" ;
106 m_done_signal.emit( static_cast<int>(m_exit.result) ) ;
107}
108
109 void GSmtp::ExecutableFilter::onTaskDone( int exit_code , const std::string & output )
110{
111 m_timer.cancelTimer() ;
112
113 // search the output for diagnostics
114 std::tie(m_response,m_reason) = parseOutput( output , "rejected" ) ;
115 if( m_response.find("filter exec error") == 0U ) // see ctor
116 {
117 m_reason = m_response ;
118 m_response = "rejected" ;
119 }
120
121 m_exit = Exit( exit_code , m_server_side ) ;
122 if( !m_exit.ok() )
123 {
124 G_WARNING( "GSmtp::ExecutableFilter::onTaskDone: " << m_prefix << " failed: "
125 << "exit code " << exit_code << ": [" << m_response << "]" ) ;
126 }
127
128 // callback
129 m_done_signal.emit( static_cast<int>(m_exit.result) ) ;
130}
131
132std::pair<std::string,std::string> GSmtp::ExecutableFilter::parseOutput( std::string s ,
133 const std::string & default_ ) const
134{
135 G_DEBUG( "GSmtp::ExecutableFilter::parseOutput: in: \"" << G::Str::printable(s) << "\"" ) ;
136
137 const std::string start_1("<<") ;
138 const std::string end_1(">>") ;
139 const std::string start_2("[[") ;
140 const std::string end_2("]]") ;
141
142 G::StringArray lines ;
143 while( G::Str::replaceAll( s , "\r\n" , "\n" ) ) {;}
144 G::Str::replaceAll( s , "\r" , "\n" ) ;
145 G::Str::splitIntoFields( s , lines , "\n" ) ;
146
147 for( auto p = lines.begin() ; p != lines.end() ; )
148 {
149 std::string line = *p ;
150 std::size_t pos_start = line.find(start_1) ;
151 std::size_t pos_end = line.find(end_1) ;
152 if( pos_start != 0U )
153 {
154 pos_start = line.find(start_2) ;
155 pos_end = line.find(end_2) ;
156 }
157 if( pos_start == 0U && pos_end != std::string::npos )
158 {
159 *p++ = G::Str::printable(line.substr(2U,pos_end-2U)) ;
160 }
161 else
162 {
163 p = lines.erase( p ) ;
164 }
165 }
166
167 G_DEBUG( "GSmtp::ExecutableFilter::parseOutput: out: [" << G::Str::join("|",lines) << "]" ) ;
168
169 std::string response = ( !lines.empty() && !lines.at(0U).empty() ) ? lines.at(0U) : default_ ;
170 std::string reason = ( lines.size() > 1U && !lines.at(1U).empty() ) ? lines.at(1U) : response ;
171 return std::make_pair( response , reason ) ;
172}
173
174 G::Slot::Signal<int> & GSmtp::ExecutableFilter::doneSignal()
175{
176 return m_done_signal ;
177}
178
179 void GSmtp::ExecutableFilter::cancel()
180{
181 m_timer.cancelTimer() ;
182 m_task.stop() ;
183}
184
185 bool GSmtp::ExecutableFilter::abandoned() const
186{
187 return m_exit.abandon() ;
188}
189
GNet::ExceptionSink
A tuple containing an ExceptionHandler interface pointer and a bound 'exception source' pointer.
Definition: gexceptionsink.h:100
GSmtp::ExecutableFilter
A Filter class that runs an external helper program.
Definition: gexecutablefilter.h:43
GSmtp::ExecutableFilter::ExecutableFilter
ExecutableFilter(GNet::ExceptionSink, FileStore &, bool server_side, const std::string &path, unsigned int timeout)
Constructor.
Definition: gexecutablefilter.cpp:32
GSmtp::ExecutableFilter::~ExecutableFilter
~ExecutableFilter() override
Destructor.
GSmtp::FileStore
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:58
G::ExecutableCommand
A structure representing an external program, holding a path and a set of arguments.
Definition: gexecutablecommand.h:44
G::Path
A Path object represents a file system path.
Definition: gpath.h:72
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.h:215
G::Str::join
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1195
G::Str::splitIntoFields
static void splitIntoFields(const std::string &in, StringArray &out, string_view ws, char escape='\0', bool remove_escapes=true)
Splits the string into fields.
Definition: gstr.cpp:1146
G::Str::printable
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:885
G::Str::replaceAll
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
Definition: gstr.cpp:287
gassert.h
gexecutablecommand.h
gexecutablefilter.h
glog.h
gnewprocess.h
gprocess.h
groot.h
gstr.h
G
Low-level classes.
Definition: galign.h:28
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
G::Slot::Signal< int >