E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

gsaslserverbasic.cpp
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 // gsaslserverbasic.cpp
19 //
20
21 #include "gdef.h"
22 #include "gsaslserverbasic.h"
23 #include "gmd5.h"
24 #include "ghash.h"
25 #include "gcram.h"
26 #include "gbase64.h"
27 #include "gstr.h"
28 #include "gtest.h"
29 #include "gdatetime.h"
30 #include "gdebug.h"
31 #include <sstream>
32 #include <algorithm>
33
34 namespace
35 {
36 const char * login_challenge_1 = "Username:" ;
37 const char * login_challenge_2 = "Password:" ;
38 }
39
40 /// \class GAuth::SaslServerBasicImp
41 /// A private pimple-pattern implementation class used by GAuth::SaslServerBasic.
42 ///
43 class GAuth::SaslServerBasicImp
44 {
45 public:
46 SaslServerBasicImp( const SaslServerSecrets & , bool ) ;
47 std::string mechanisms( const std::string & ) const ;
48 bool init( const std::string & mechanism ) ;
49 std::string mechanism() const ;
50 std::string initialChallenge() const ;
51 std::string apply( const std::string & response , bool & done ) ;
52 bool trusted( const GNet::Address & ) const ;
53 bool trustedCore( const std::string & , const GNet::Address & ) const ;
54 bool active() const ;
55 std::string id() const ;
56 bool authenticated() const ;
57
58 private:
59 bool m_allow_apop ;
60 bool m_first_apply ;
61 const SaslServerSecrets & m_secrets ;
62 G::StringArray m_mechanisms ; // that have secrets
63 std::string m_mechanism ;
64 std::string m_challenge ;
65 bool m_authenticated ;
66 std::string m_id ;
67 std::string m_trustee ;
68 bool m_advertise_login ;
69 bool m_advertise_plain ;
70 bool m_advertise_cram_md5 ;
71 } ;
72
73 // ===
74
75 GAuth::SaslServerBasicImp::SaslServerBasicImp( const SaslServerSecrets & secrets , bool allow_apop ) :
76 m_allow_apop(allow_apop) ,
77 m_first_apply(true) ,
78 m_secrets(secrets) ,
79 m_authenticated(false)
80 {
81 m_mechanisms = Cram::hashTypes( "CRAM-" ) ;
82 m_mechanisms.push_back( "PLAIN" ) ;
83 m_mechanisms.push_back( "LOGIN" ) ;
84 if( G::Test::enabled("sasl-server-oauth") )
85 m_mechanisms.push_back( "XOAUTH2" ) ; // for testing
86 }
87
88 std::string GAuth::SaslServerBasicImp::mechanisms( const std::string & sep ) const
89 {
90 return G::Str::join( sep , m_mechanisms ) ;
91 }
92
93 bool GAuth::SaslServerBasicImp::init( const std::string & mechanism_in )
94 {
95 m_first_apply = true ;
96 m_authenticated = false ;
97 m_id.erase() ;
98 m_trustee.erase() ;
99 m_challenge.erase() ;
100 m_mechanism.erase() ;
101
102 std::string mechanism = G::Str::upper( mechanism_in ) ;
103 if( m_allow_apop && mechanism == "APOP" )
104 {
105 m_mechanism = mechanism ;
106 m_challenge = Cram::challenge() ;
107 return true ;
108 }
109 else if( std::find(m_mechanisms.begin(),m_mechanisms.end(),mechanism) == m_mechanisms.end() )
110 {
111 G_DEBUG( "GAuth::SaslServerBasicImp::init: requested mechanism [" << mechanism << "] is not in our list" ) ;
112 return false ;
113 }
114 else if( mechanism.find("CRAM-") == 0U )
115 {
116 m_mechanism = mechanism ;
117 m_challenge = Cram::challenge() ;
118 return true ;
119 }
120 else
121 {
122 m_mechanism = mechanism ;
123 return true ;
124 }
125 }
126
127 std::string GAuth::SaslServerBasicImp::initialChallenge() const
128 {
129 // see RFC-4422 section 5
130 if( m_mechanism == "PLAIN" ) // "client-first"
131 return std::string() ;
132 else if( m_mechanism == "XOAUTH2" ) // for testing -- "client-first"
133 return std::string() ;
134 else if( m_mechanism == "LOGIN" ) // "variable"
135 return login_challenge_1 ;
136 else // CRAM-X "server-first"
137 return m_challenge ;
138 }
139
140 std::string GAuth::SaslServerBasicImp::apply( const std::string & response , bool & done )
141 {
142 G_DEBUG( "GAuth::SaslServerBasic::apply: response: \"" << G::Str::printable(response) << "\"" ) ;
143
144 bool first_apply = m_first_apply ;
145 m_first_apply = false ;
146
147 done = false ;
148 std::string id ;
149 Secret secret = Secret::none() ;
150 std::string next_challenge ;
151
152 if( m_mechanism.find("CRAM-") == 0U || m_mechanism == "APOP" )
153 {
154 id = Cram::id( response ) ;
155 secret = Secret::none() ;
156 if( !id.empty() )
157 {
158 std::string hash_type = m_mechanism == "APOP" ? "MD5" : m_mechanism.substr(5U) ;
159 secret = m_secrets.serverSecret( hash_type , id ) ;
160 if( !secret.valid() )
161 secret = m_secrets.serverSecret( "plain" , id ) ;
162 }
163 if( !secret.valid() )
164 {
165 m_authenticated = false ;
166 }
167 else
168 {
169 m_id = id ;
170 m_authenticated =
171 m_mechanism == "APOP" ?
172 Cram::validate( "MD5" , false , secret , m_challenge , response ) :
173 Cram::validate( m_mechanism.substr(5U) , true , secret , m_challenge , response ) ;
174 }
175 done = true ;
176 }
177 else if( m_mechanism == "PLAIN" )
178 {
179 // PLAIN has a single response containing three nul-separated fields
180 std::string sep( 1U , '\0' ) ;
181 std::string id_pwd_in = G::Str::tail( response , sep ) ;
182 id = G::Str::head( id_pwd_in , sep ) ;
183 std::string pwd_in = G::Str::tail( id_pwd_in , sep ) ;
184 secret = m_secrets.serverSecret( "plain" , id ) ;
185
186 m_authenticated = secret.valid() && !id.empty() && !pwd_in.empty() && pwd_in == secret.key() ;
187 m_id = id ;
188 done = true ;
189 }
190 else if( m_mechanism == "XOAUTH2" && first_apply ) // for testing
191 {
192 if( G::Test::enabled("sasl-server-oauth-pass") )
193 {
194 m_id = "test" ;
195 m_authenticated = true ;
196 done = true ;
197 }
198 else
199 {
200 next_challenge = "just testing" ;
201 done = false ;
202 }
203 }
204 else if( m_mechanism == "XOAUTH2" ) // for testing
205 {
206 m_authenticated = false ;
207 done = true ;
208 }
209 else if( first_apply ) // LOGIN username
210 {
211 // LOGIN uses two prompts; the first response is the username and the second is the password
212 G_ASSERT( m_mechanism == "LOGIN" ) ;
213 id = m_id = response ;
214 if( !m_id.empty() )
215 next_challenge = login_challenge_2 ;
216 }
217 else // LOGIN password
218 {
219 G_ASSERT( m_mechanism == "LOGIN" ) ;
220 id = m_id ;
221 secret = m_secrets.serverSecret( "plain" , m_id ) ;
222 m_authenticated = secret.valid() && !response.empty() && response == secret.key() ;
223 done = true ;
224 }
225
226 if( done )
227 {
228 std::ostringstream ss ;
229 ss
230 << (m_authenticated?"successful":"failed") << " authentication of remote client using mechanism ["
231 << G::Str::lower(m_mechanism) << "] and " << secret.info(id) ;
232 if( m_authenticated )
233 G_LOG( "GAuth::SaslServerBasicImp::apply: " << ss.str() ) ;
234 else
235 G_WARNING( "GAuth::SaslServerBasicImp::apply: " << ss.str() ) ;
236 }
237
238 return next_challenge ;
239 }
240
241 bool GAuth::SaslServerBasicImp::trusted( const GNet::Address & address ) const
242 {
243 G_DEBUG( "GAuth::SaslServerBasicImp::trusted: \"" << address.hostPartString() << "\"" ) ;
244 G::StringArray wc = address.wildcards() ;
245 for( G::StringArray::iterator p = wc.begin() ; p != wc.end() ; ++p )
246 {
247 if( trustedCore(*p,address) )
248 return true ;
249 }
250 return false ;
251 }
252
253 bool GAuth::SaslServerBasicImp::trustedCore( const std::string & address_wildcard , const GNet::Address & address ) const
254 {
255 G_DEBUG( "GAuth::SaslServerBasicImp::trustedCore: \"" << address_wildcard << "\", \"" << address.hostPartString() << "\"" ) ;
256 std::pair<std::string,std::string> pair = m_secrets.serverTrust( address_wildcard ) ;
257 std::string & trustee = pair.first ;
258 if( !trustee.empty() )
259 {
260 G_LOG( "GAuth::SaslServer::trusted: trusting [" << address.hostPartString() << "]: "
261 << "matched [" << address_wildcard << "] " << pair.second ) ;
262 const_cast<SaslServerBasicImp*>(this)->m_trustee = trustee ;
263 return true ;
264 }
265 else
266 {
267 return false ;
268 }
269 }
270
271 bool GAuth::SaslServerBasicImp::active() const
272 {
273 return m_secrets.valid() ;
274 }
275
276 std::string GAuth::SaslServerBasicImp::mechanism() const
277 {
278 return m_mechanism ;
279 }
280
281 std::string GAuth::SaslServerBasicImp::id() const
282 {
283 return m_authenticated ? m_id : m_trustee ;
284 }
285
286 bool GAuth::SaslServerBasicImp::authenticated() const
287 {
288 return m_authenticated ;
289 }
290
291 // ===
292
293 GAuth::SaslServerBasic::SaslServerBasic( const SaslServerSecrets & secrets , bool allow_apop ) :
294 m_imp(new SaslServerBasicImp(secrets,allow_apop))
295 {
296 }
297
298 GAuth::SaslServerBasic::~SaslServerBasic()
299 {
300 delete m_imp ;
301 }
302
303 std::string GAuth::SaslServerBasic::mechanisms( char c ) const
304 {
305 return m_imp->mechanisms( std::string(1U,c) ) ;
306 }
307
308 std::string GAuth::SaslServerBasic::mechanism() const
309 {
310 return m_imp->mechanism() ;
311 }
312
313 bool GAuth::SaslServerBasic::trusted( const GNet::Address & address ) const
314 {
315 return m_imp->trusted( address ) ;
316 }
317
318 bool GAuth::SaslServerBasic::active() const
319 {
320 return m_imp->active() ;
321 }
322
323 bool GAuth::SaslServerBasic::init( const std::string & mechanism )
324 {
325 return m_imp->init( mechanism ) ;
326 }
327
328 bool GAuth::SaslServerBasic::mustChallenge() const
329 {
330 std::string m = G::Str::upper( m_imp->mechanism() ) ; // just in case
331 return m != "PLAIN" && m != "LOGIN" ;
332 }
333
334 std::string GAuth::SaslServerBasic::initialChallenge() const
335 {
336 return m_imp->initialChallenge() ;
337 }
338
339 std::string GAuth::SaslServerBasic::apply( const std::string & response , bool & done )
340 {
341 return m_imp->apply( response , done ) ;
342 }
343
344 bool GAuth::SaslServerBasic::authenticated() const
345 {
346 return m_imp->authenticated() ;
347 }
348
349 std::string GAuth::SaslServerBasic::id() const
350 {
351 return m_imp->id() ;
352 }
353
354 bool GAuth::SaslServerBasic::requiresEncryption() const
355 {
356 return false ;
357 }
358
GAuth::SaslServerBasic::mechanism
virtual std::string mechanism() const override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:308
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
GAuth::SaslServerBasic::mechanisms
virtual std::string mechanisms(char sep= ' ') const override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:303
G::Str::tail
static std::string tail(const std::string &in, size_type pos, const std::string &default_=std::string())
Returns the last part of the string after the given position.
Definition: gstr.cpp:1102
GAuth::SaslServerBasic::~SaslServerBasic
virtual ~SaslServerBasic()
Destructor.
Definition: gsaslserverbasic.cpp:298
GAuth::SaslServerBasic::mustChallenge
virtual bool mustChallenge() const override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:328
gmd5.h
GAuth::SaslServerBasic::apply
virtual std::string apply(const std::string &response, bool &done) override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:339
G::Str::head
static std::string head(const std::string &in, size_type pos, const std::string &default_=std::string())
Returns the first part of the string up to just before the given position.
Definition: gstr.cpp:1088
gcram.h
GNet::Address
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:54
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
GAuth::SaslServerBasic::active
virtual bool active() const override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:318
GAuth::SaslServerBasic::requiresEncryption
virtual bool requiresEncryption() const override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:354
gtest.h
GAuth::Cram::validate
static bool validate(const std::string &hash_type, bool hmac, const Secret &secret, const std::string &challenge, const std::string &response)
Validates the response with respect to the original challenge.
Definition: gcram.cpp:110
GAuth::SaslServerSecrets
An interface used by GAuth::SaslServer to obtain authentication secrets.
Definition: gsaslserversecrets.h:39
GAuth::Cram::challenge
static std::string challenge()
Returns a challenge string that incorporates a random number and the current time.
Definition: gcram.cpp:208
G::Str::lower
static std::string lower(const std::string &s)
Returns a copy of 's' in which all Latin-1 uppercase characters have been replaced by lowercase chara...
Definition: gstr.cpp:599
GAuth::Cram::id
static std::string id(const std::string &response)
Returns the leading id part of the response.
Definition: gcram.cpp:130
GAuth::SaslServerBasic::authenticated
virtual bool authenticated() const override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:344
gstr.h
GNet::Address::wildcards
G::StringArray wildcards() const
Returns an ordered list of wildcard strings that match this address.
Definition: gaddress_ipv4.cpp:194
G::Test::enabled
static bool enabled()
Returns true if test features are enabled.
Definition: gtest.cpp:69
ghash.h
GNet::Address::hostPartString
std::string hostPartString() const
Returns a string which represents the network address for debugging and diagnostics purposes...
Definition: gaddress_ipv4.cpp:134
GAuth::SaslServerBasicImp
A private pimple-pattern implementation class used by GAuth::SaslServerBasic.
Definition: gsaslserverbasic.cpp:43
G::Str::upper
static std::string upper(const std::string &s)
Returns a copy of 's' in which all Latin-1 lowercase characters have been replaced by uppercase chara...
Definition: gstr.cpp:619
gsaslserverbasic.h
GAuth::SaslServerBasic::trusted
virtual bool trusted(const GNet::Address &) const override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:313
gdatetime.h
GAuth::Secret::none
static Secret none()
Factory function that returns a secret that is not valid() and has an empty id(). ...
Definition: gsecret.cpp:90
gbase64.h
G::Str::join
static std::string join(const std::string &sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1023
GAuth::SaslServerBasic::init
virtual bool init(const std::string &mechanism) override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:323
GAuth::SaslServerBasic::initialChallenge
virtual std::string initialChallenge() const override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:334
GAuth::SaslServerBasic::SaslServerBasic
SaslServerBasic(const SaslServerSecrets &, bool allow_apop)
Constructor.
Definition: gsaslserverbasic.cpp:293
gdebug.h
GAuth::Cram::hashTypes
static G::StringArray hashTypes(const std::string &prefix=std::string(), bool require_state=false)
Returns a list of supported hash types, such as "MD5" and "SHA1", ordered with the strongest first...
Definition: gcram.cpp:183
GAuth::SaslServerBasic::id
virtual std::string id() const override
Override from GAuth::SaslServer.
Definition: gsaslserverbasic.cpp:349