E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

gspamclient.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2001-2018 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 // gspamclient.cpp
19 //
20
21 #include "gdef.h"
22 #include "gsmtp.h"
23 #include "gstr.h"
24 #include "gfile.h"
25 #include "gtest.h"
26 #include "gspamclient.h"
27 #include "gassert.h"
28 #include <sstream>
29
30 std::string GSmtp::SpamClient::m_username ;
31
32 GSmtp::SpamClient::SpamClient( const GNet::Location & location , bool read_only ,
33 unsigned int connect_timeout , unsigned int response_timeout ) :
34 GNet::Client(location,connect_timeout,response_timeout,0U,GNet::LineBufferConfig::newline()) ,
35 m_busy(false) ,
36 m_timer(*this,&SpamClient::onTimeout,*this) ,
37 m_request(*this) ,
38 m_response(read_only)
39 {
40 G_LOG( "GSmtp::SpamClient::ctor: spam connection to [" << location << "]" ) ;
41 G_DEBUG( "GSmtp::SpamClient::ctor: spam read/only=" << read_only ) ;
42 G_DEBUG( "GSmtp::SpamClient::ctor: spam connection timeout " << connect_timeout ) ;
43 G_DEBUG( "GSmtp::SpamClient::ctor: spam response timeout " << response_timeout ) ;
44 }
45
46 GSmtp::SpamClient::~SpamClient()
47 {
48 G_DEBUG( "GSmtp::SpamClient::dtor: busy=" << m_busy ) ;
49 }
50
51 void GSmtp::SpamClient::username( const std::string & username )
52 {
53 m_username = username ;
54 }
55
56 bool GSmtp::SpamClient::busy() const
57 {
58 return m_busy ;
59 }
60
61 void GSmtp::SpamClient::request( const std::string & path )
62 {
63 G_DEBUG( "GSmtp::SpamClient::request: path=" << path ) ;
64 if( m_busy )
65 throw Error( "protocol error" ) ;
66 m_busy = true ;
67 m_path = path ;
68 m_timer.startTimer( 0U ) ;
69 }
70
71 void GSmtp::SpamClient::onTimeout()
72 {
73 G_DEBUG( "GSmtp::SpamClient::onTimeout: connected=" << connected() ) ;
74 if( connected() )
75 start() ;
76 }
77
78 void GSmtp::SpamClient::onDelete( const std::string & reason )
79 {
80 }
81
82 void GSmtp::SpamClient::onSecure( const std::string & )
83 {
84 }
85
86 void GSmtp::SpamClient::onConnect()
87 {
88 if( m_busy )
89 start() ;
90 }
91
92 void GSmtp::SpamClient::start()
93 {
94 m_request.send( m_path , m_username ) ;
95 }
96
97 void GSmtp::SpamClient::onSendComplete()
98 {
99 m_request.sendMore() ;
100 }
101
102 bool GSmtp::SpamClient::onReceive( const char * line_data , size_t line_size , size_t )
103 {
104 m_response.add( m_path , std::string(line_data,line_size) ) ;
105 if( m_response.complete() )
106 eventSignal().emit( "spam" , m_response.result() ) ;
107 return true ;
108 }
109
110 // ==
111
112 GSmtp::SpamClient::Request::Request( Client & client ) :
113 m_client(&client) ,
114 m_buffer(10240U)
115 {
116 }
117
118 void GSmtp::SpamClient::Request::send( const std::string & path , const std::string & username )
119 {
120 G_LOG( "GSmtp::SpamClient::Request::send: spam request for [" << path << "]" ) ;
121 m_stream.open( path.c_str() , std::ios_base::binary ) ;
122 if( !m_stream.good() )
123 throw SpamClient::Error( "cannot read content file" , path ) ;
124
125 std::string file_size = G::File::sizeString(path) ;
126 G_DEBUG( "GSmtp::SpamClient::Request::send: spam request file size: " << file_size ) ;
127
128 std::ostringstream ss ;
129 std::string eol = "\r\n" ;
130 ss << "PROCESS SPAMC/1.4" << eol ;
131 if( !username.empty() )
132 ss << "User: " << username << eol ;
133 ss << "Content-length: " << file_size << eol ;
134 ss << eol ;
135
136 bool sent = m_client->send( ss.str() ) ;
137 while( sent )
138 {
139 sent = sendMore() ;
140 }
141 G_DEBUG( "GSmtp::SpamClient::Request::send: spam sent" ) ;
142 }
143
144 bool GSmtp::SpamClient::Request::sendMore()
145 {
146 m_stream.read( &m_buffer[0] , m_buffer.size() ) ;
147 std::streamsize n = m_stream.gcount() ;
148 if( n <= 0 )
149 {
150 G_LOG( "GSmtp::SpamClient::Request::sendMore: spam request done" ) ;
151 return false ;
152 }
153 else
154 {
155 G_DEBUG( "GSmtp::SpamClient::Request::sendMore: spam request sending " << n << " bytes" ) ;
156 return m_client->send( std::string(&m_buffer[0],static_cast<size_t>(n)) ) ;
157 }
158 }
159
160 // ==
161
162 GSmtp::SpamClient::Response::Response( bool read_only ) :
163 m_read_only(read_only) ,
164 m_state(0) ,
165 m_content_length(0U) ,
166 m_size(0U)
167 {
168 }
169
170 GSmtp::SpamClient::Response::~Response()
171 {
172 if( m_stream.is_open() )
173 {
174 m_stream.close() ;
175 G::File::remove( m_path_tmp.c_str() , G::File::NoThrow() ) ;
176 }
177 }
178
179 void GSmtp::SpamClient::Response::add( const std::string & path , const std::string & line )
180 {
181 if( m_state == 0 && !ok(line) )
182 {
183 throw SpamClient::Error( "invalid response" , G::Str::printable(G::Str::trimmed(line,G::Str::ws())) ) ;
184 }
185 else if( m_state == 0 )
186 {
187 G_DEBUG( "GSmtp::SpamClient::Request::sendMore: spam response" ) ;
188 m_path_final = path ;
189 m_path_tmp = path + ".spamd" ;
190 if( !m_read_only && !m_stream.is_open() )
191 {
192 m_stream.open( m_path_tmp.c_str() , std::ios_base::binary ) ;
193 if( !m_stream.good() )
194 throw SpamClient::Error( "cannot write temporary content file" , m_path_tmp ) ;
195 }
196 m_content_length = m_size = 0U ;
197 m_state = 1 ;
198 }
199 if( m_state == 1 ) // spamc/spamd headers
200 {
201 G_LOG( "GSmtp::SpamClient::Response::add: spam response line: [" << G::Str::printable(G::Str::trimmed(line,G::Str::ws())) << "]" ) ;
202 if( line.find("Spam:") == 0U )
203 m_result = G::Str::trimmed( line.substr(5U) , G::Str::ws() ) ;
204 else if( G::Str::imatch(line.substr(0U,15U),"Content-length:") )
205 m_content_length = G::Str::toUInt( G::Str::trimmed(line.substr(15U),G::Str::ws()) ) ;
206 else if( ( line.empty() || line == "\r" ) && m_content_length == 0U )
207 throw SpamClient::Error( "invalid response headers" ) ;
208 else if( line.empty() || line == "\r" )
209 m_state = 2 ;
210 }
211 else if( m_state == 2 ) // email content
212 {
213 m_size += ( line.size() + 1U ) ;
214
215 if( m_stream.is_open() )
216 m_stream << line << "\n" ;
217
218 if( m_size >= m_content_length )
219 {
220 if( m_size != m_content_length )
221 G_WARNING( "GSmtp::SpamClient::Response::add: incorrect content length in spam response" ) ;
222 G_LOG( "GSmtp::SpamClient::add: spam response size: " << m_content_length ) ;
223
224 if( m_stream.is_open() )
225 {
226 m_stream.close() ;
227 if( m_stream.fail() )
228 throw SpamClient::Error( "cannot write temporary content file" , m_path_tmp ) ;
229
230 G::File::remove( m_path_final ) ;
231 G::File::rename( m_path_tmp , m_path_final ) ;
232 }
233
234 m_state = 3 ;
235 }
236 }
237 }
238
239 bool GSmtp::SpamClient::Response::complete() const
240 {
241 return m_state == 3 ;
242 }
243
244 bool GSmtp::SpamClient::Response::ok( const std::string & line ) const
245 {
246 // eg. "SPAMD/1.0 99 Timeout", "SPAMD/1.1 0 OK"
247 if( line.empty() ) return false ;
248 if( line.find("SPAMD/") != 0U ) return false ;
249 G::StringArray parts = G::Str::splitIntoTokens( line , G::Str::ws() ) ;
250 if( parts.size() < 2U ) return false ;
251 return parts.at(1U) == "0" ;
252 }
253
254 std::string GSmtp::SpamClient::Response::result() const
255 {
256 if( G::Str::imatch(m_result.substr(0U,5U),"False") )
257 return std::string() ;
258 else
259 return m_result ; // eg. "True ; 4.5 / 5.0"
260 }
261
262 /// \file gspamclient.cpp
GSmtp::SpamClient::busy
bool busy() const
Returns true after request() and before the subsequent event signal.
Definition: gspamclient.cpp:56
G::Str::printable
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
Definition: gstr.cpp:701
GSmtp::SpamClient::SpamClient
SpamClient(const GNet::Location &host_and_service, bool read_only, unsigned int connect_timeout, unsigned int response_timeout)
Constructor.
Definition: gspamclient.cpp:32
GSmtp::SpamClient::onReceive
virtual bool onReceive(const char *, size_t, size_t) override
Override from GNet::Client.
Definition: gspamclient.cpp:102
GNet
Network classes.
Definition: gaddress.h:29
gspamclient.h
G::Str::imatch
static bool imatch(const std::string &, const std::string &)
Returns true if the two strings are the same, ignoring Latin-1 case.
Definition: gstr.cpp:1220
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
G::Str::splitIntoTokens
static void splitIntoTokens(const std::string &in, StringArray &out, const std::string &ws)
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:908
GSmtp::SpamClient::username
static void username(const std::string &)
Sets the username used in the network protocol.
Definition: gspamclient.cpp:51
gtest.h
G::File::rename
static bool rename(const Path &from, const Path &to, const NoThrow &)
Renames the file. Returns false on error.
Definition: gfile.cpp:45
GSmtp::SpamClient::onSecure
virtual void onSecure(const std::string &) override
Override from GNet::SocketProtocolSink.
Definition: gspamclient.cpp:82
GSmtp::SpamClient::onDelete
virtual void onDelete(const std::string &) override
Override from GNet::HeapClient.
Definition: gspamclient.cpp:78
GNet::Location
A class that holds a host/service name pair and the preferred address family (if any), and also the results of a name-to-address lookup, ie.
Definition: glocation.h:50
G::Str::toUInt
static unsigned int toUInt(const std::string &s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:488
gstr.h
gassert.h
GSmtp::SpamClient::~SpamClient
virtual ~SpamClient()
Destructor.
Definition: gspamclient.cpp:46
gfile.h
GSmtp::SpamClient
A client class that interacts with a remote process using a protocol somewhat similar to the spamassa...
Definition: gspamclient.h:46
G::Str::trimmed
static std::string trimmed(const std::string &s, const std::string &ws)
Returns a trim()med version of s.
Definition: gstr.cpp:251
G::File::remove
static bool remove(const Path &path, const NoThrow &)
Deletes the file or directory. Returns false on error.
Definition: gfile.cpp:29
GSmtp::Client
A class which acts as an SMTP client, extracting messages from a message store and forwarding them to...
Definition: gsmtpclient.h:52
G::File::NoThrow
An overload discriminator class for File methods.
Definition: gfile.h:53
GSmtp::SpamClient::onSendComplete
virtual void onSendComplete() override
Override from GNet::SimpleClient.
Definition: gspamclient.cpp:97
GSmtp::SpamClient::onConnect
virtual void onConnect() override
Override from GNet::SimpleClient.
Definition: gspamclient.cpp:86
G::File::sizeString
static std::string sizeString(const Path &file)
Returns the file's size in string format.
Definition: gfile_unix.cpp:96
gsmtp.h
G::Str::ws
static std::string ws()
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1078
GSmtp::SpamClient::request
void request(const std::string &file_path)
Starts sending a request that comprises a few http-like header lines followed by the contents of the ...
Definition: gspamclient.cpp:61