E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

gsaslclient.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 // gsaslclient.cpp
19 //
20
21 #include "gdef.h"
22 #include "gsaslclient.h"
23 #include "gssl.h"
24 #include "gmd5.h"
25 #include "ghash.h"
26 #include "gcram.h"
27 #include "gbase64.h"
28 #include "gstr.h"
29 #include "glogoutput.h"
30 #include "gdebug.h"
31 #include <algorithm>
32 #include <sstream>
33
34 namespace
35 {
36 const char * login_challenge_1 = "Username:" ;
37 const char * login_challenge_2 = "Password:" ;
38 }
39
40 /// \class GAuth::SaslClientImp
41 /// A private pimple-pattern implementation class used by GAuth::SaslClient.
42 ///
43 class GAuth::SaslClientImp
44 {
45 public:
46 typedef SaslClient::Response Response ;
47 explicit SaslClientImp( const SaslClientSecrets & ) ;
48 bool active() const ;
49 std::string preferred( const G::StringArray & ) const ;
50 Response response( const std::string & mechanism , const std::string & challenge ) const ;
51 bool next() ;
52 std::string preferred() const ;
53 std::string id() const ;
54 std::string info() const ;
55 static bool match( const G::StringArray & mechanisms , const std::string & ) ;
56
57 private:
58 const SaslClientSecrets & m_secrets ;
59 mutable G::StringArray m_mechanisms ;
60 mutable std::string m_info ;
61 mutable std::string m_id ;
62 std::string PLAIN ;
63 std::string LOGIN ;
64 } ;
65
66 // ===
67
68 GAuth::SaslClientImp::SaslClientImp( const SaslClientSecrets & secrets ) :
69 m_secrets(secrets) ,
70 PLAIN("PLAIN") ,
71 LOGIN("LOGIN")
72 {
73 }
74
75 std::string GAuth::SaslClientImp::preferred( const G::StringArray & server_mechanisms ) const
76 {
77 // short-circuit if no secrets file
78 if( !active() )
79 return std::string() ;
80
81 // if we have a plaintext password then we can use any cram
82 // mechanism for which we have a hash function -- otherwise
83 // we can use cram mechanisms where we have a hashed password
84 // of the correct type and the hash function is capable of
85 // initialisation with an intermediate state
86 //
87 G::StringArray our_list = Cram::hashTypes( "CRAM-" , !m_secrets.clientSecret("plain").valid() ) ;
88 for( G::StringArray::iterator p = our_list.begin() ; p != our_list.end() ; )
89 {
90 if( m_secrets.clientSecret((*p).substr(5U)).valid() )
91 ++p ;
92 else
93 p = our_list.erase( p ) ;
94 }
95 if( m_secrets.clientSecret("oauth").valid() )
96 {
97 our_list.push_back( "XOAUTH2" ) ;
98 }
99 if( m_secrets.clientSecret("plain").valid() )
100 {
101 our_list.push_back( PLAIN ) ;
102 our_list.push_back( LOGIN ) ;
103 }
104
105 // build the list of mechanisms that we can use with the server
106 m_mechanisms.clear() ;
107 for( G::StringArray::iterator p = our_list.begin() ; p != our_list.end() ; ++p )
108 {
109 if( match(server_mechanisms,*p) )
110 {
111 m_mechanisms.push_back( *p ) ;
112 }
113 }
114
115 G_DEBUG( "GAuth::SaslClientImp::preferred: server mechanisms: [" << G::Str::join(",",server_mechanisms) << "]" ) ;
116 G_DEBUG( "GAuth::SaslClientImp::preferred: our mechanisms: [" << G::Str::join(",",our_list) << "]" ) ;
117 G_DEBUG( "GAuth::SaslClientImp::preferred: usable mechanisms: [" << G::Str::join(",",m_mechanisms) << "]" ) ;
118
119 return m_mechanisms.empty() ? std::string() : m_mechanisms.at(0U) ;
120 }
121
122 bool GAuth::SaslClientImp::next()
123 {
124 if( !m_mechanisms.empty() )
125 m_mechanisms.erase( m_mechanisms.begin() ) ;
126 return !m_mechanisms.empty() ;
127 }
128
129 std::string GAuth::SaslClientImp::preferred() const
130 {
131 return m_mechanisms.empty() ? std::string() : m_mechanisms.at(0U) ;
132 }
133
134 GAuth::SaslClient::Response GAuth::SaslClientImp::response( const std::string & mechanism ,
135 const std::string & challenge ) const
136 {
137 Response rsp ;
138 rsp.error = true ;
139 rsp.final = false ;
140 rsp.sensitive = true ;
141
142 Secret secret = Secret::none() ;
143 if( mechanism.find("CRAM-") == 0U )
144 {
145 std::string hash_type = mechanism.substr( 5U ) ;
146 secret = m_secrets.clientSecret( hash_type ) ;
147 if( !secret.valid() )
148 secret = m_secrets.clientSecret( "plain" ) ;
149 rsp.data = Cram::response( hash_type , true , secret , challenge , secret.id() ) ;
150 rsp.error = rsp.data.empty() ;
151 rsp.final = true ;
152 }
153 else if( mechanism == "APOP" )
154 {
155 secret = m_secrets.clientSecret( "MD5" ) ;
156 rsp.data = Cram::response( "MD5" , false , secret , challenge , secret.id() ) ;
157 rsp.error = rsp.data.empty() ;
158 rsp.final = true ;
159 }
160 else if( mechanism == PLAIN )
161 {
162 secret = m_secrets.clientSecret( "plain" ) ;
163 const std::string sep( 1U , '\0' ) ;
164 rsp.data = sep + secret.id() + sep + secret.key() ;
165 rsp.error = rsp.data.empty() ;
166 rsp.final = true ;
167 }
168 else if( mechanism == LOGIN && challenge == login_challenge_1 )
169 {
170 secret = m_secrets.clientSecret( "plain" ) ;
171 rsp.data = secret.id() ;
172 rsp.error = rsp.data.empty() ;
173 rsp.final = false ;
174 rsp.sensitive = false ;
175 }
176 else if( mechanism == LOGIN && challenge == login_challenge_2 )
177 {
178 secret = m_secrets.clientSecret( "plain" ) ;
179 rsp.data = secret.key() ;
180 rsp.error = rsp.data.empty() ;
181 rsp.final = true ;
182 }
183 else if( mechanism == "XOAUTH2" && challenge.empty() )
184 {
185 secret = m_secrets.clientSecret( "oauth" ) ;
186 rsp.data = secret.key() ;
187 rsp.error = rsp.data.empty() ;
188 rsp.final = true ; // not well-defined, may get an informational challenge
189 rsp.sensitive = true ;
190 }
191 else if( mechanism == "XOAUTH2" )
192 {
193 secret = m_secrets.clientSecret( "oauth" ) ;
194 rsp.data.clear() ; // information-only challenge gets an empty response
195 rsp.error = false ;
196 rsp.final = true ;
197 rsp.sensitive = false ;
198 }
199
200 if( rsp.final )
201 {
202 std::ostringstream ss ;
203 ss << "using mechanism [" << G::Str::lower(mechanism) << "] and " << secret.info() ;
204 m_info = ss.str() ;
205 m_id = secret.id() ;
206 }
207
208 return rsp ;
209 }
210
211 std::string GAuth::SaslClientImp::id() const
212 {
213 return m_id ;
214 }
215
216 std::string GAuth::SaslClientImp::info() const
217 {
218 return m_info ;
219 }
220
221 bool GAuth::SaslClientImp::active() const
222 {
223 return m_secrets.valid() ;
224 }
225
226 bool GAuth::SaslClientImp::match( const G::StringArray & mechanisms , const std::string & mechanism )
227 {
228 return std::find(mechanisms.begin(),mechanisms.end(),mechanism) != mechanisms.end() ;
229 }
230
231 // ===
232
233 GAuth::SaslClient::SaslClient( const SaslClientSecrets & secrets ) :
234 m_imp(new SaslClientImp(secrets) )
235 {
236 }
237
238 GAuth::SaslClient::~SaslClient()
239 {
240 delete m_imp ;
241 }
242
243 bool GAuth::SaslClient::active() const
244 {
245 return m_imp->active() ;
246 }
247
248 GAuth::SaslClient::Response GAuth::SaslClient::response( const std::string & mechanism , const std::string & challenge ) const
249 {
250 return m_imp->response( mechanism , challenge ) ;
251 }
252
253 std::string GAuth::SaslClient::preferred( const G::StringArray & server_mechanisms ) const
254 {
255 return m_imp->preferred( server_mechanisms ) ;
256 }
257
258 bool GAuth::SaslClient::next()
259 {
260 return m_imp->next() ;
261 }
262
263 std::string GAuth::SaslClient::preferred() const
264 {
265 return m_imp->preferred() ;
266 }
267
268 std::string GAuth::SaslClient::id() const
269 {
270 return m_imp->id() ;
271 }
272
273 std::string GAuth::SaslClient::info() const
274 {
275 return m_imp->info() ;
276 }
277
gssl.h
An interface to an underlying TLS library.
GAuth::SaslClient::info
std::string info() const
Returns logging and diagnostic information, valid after the last response().
Definition: gsaslclient.cpp:273
GAuth::SaslClientSecrets
An interface used by GAuth::SaslClient to obtain a client id and its authentication secret...
Definition: gsaslclientsecrets.h:38
gsaslclient.h
gmd5.h
GAuth::SaslClient::SaslClient
SaslClient(const SaslClientSecrets &secrets)
Constructor. The secrets reference is kept.
Definition: gsaslclient.cpp:233
gcram.h
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
glogoutput.h
GAuth::SaslClient::active
bool active() const
Returns true if the constructor's secrets object is valid.
Definition: gsaslclient.cpp:243
GAuth::SaslClient::response
Response response(const std::string &mechanism, const std::string &challenge) const
Returns a response to the given challenge.
Definition: gsaslclient.cpp:248
GAuth::SaslClient::id
std::string id() const
Returns the authentication id, valid after the last response().
Definition: gsaslclient.cpp:268
GAuth::SaslClientImp
A private pimple-pattern implementation class used by GAuth::SaslClient.
Definition: gsaslclient.cpp:43
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
gstr.h
ghash.h
GAuth::Cram::response
static std::string response(const std::string &hash_type, bool hmac, const Secret &secret, const std::string &challenge, const std::string &response_prefix)
Constructs a response to a challenge comprising the response-prefix, space, and digest-or-hmac of key...
Definition: gcram.cpp:90
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
GAuth::SaslClient::~SaslClient
~SaslClient()
Destructor.
Definition: gsaslclient.cpp:238
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::SaslClient::next
bool next()
Moves to the next preferred mechanism.
Definition: gsaslclient.cpp:258
gdebug.h
GAuth::SaslClient::preferred
std::string preferred() const
Returns the name of the current preferred mechanism after next() returns true.
Definition: gsaslclient.cpp:263
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::SaslClient::Response
Result structure returned from GAuth::SaslClient::response.
Definition: gsaslclient.h:42