E-MailRelay

gpam_linux.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 gpam_linux.cpp
19///
20// See: http://www.linux-pam.org/Linux-PAM-html/
21//
22
23#include "gdef.h"
24#include "gpam.h"
25#include "glog.h"
26#include "gstr.h"
27#include "gexception.h"
28#include "gassert.h"
29#include <cstdlib>
30#include <cstring>
31#include <sstream>
32#include <stdexcept>
33#include <new>
34
35#if GCONFIG_HAVE_PAM_IN_INCLUDE
36#include <pam_appl.h>
37#else
38#if GCONFIG_HAVE_PAM_IN_PAM
39#include <pam/pam_appl.h>
40#else
41#include <security/pam_appl.h>
42#endif
43#endif
44#if GCONFIG_PAM_CONST
45#define G_PAM_CONST const
46#else
47#define G_PAM_CONST
48#endif
49
50//| \class G::PamImp
51/// A pimple-pattern implementation class for G::Pam.
52///
53 class G::PamImp
54{
55 public:
56 using Handle = pam_handle_t * ;
57 using Conversation = struct pam_conv ;
58 PamImp( Pam & pam , const std::string & app , const std::string & user , bool silent ) ;
59 Handle hpam() const ;
60 bool silent() const ;
61 bool authenticate( bool ) ;
62 void check( const std::string & , int ) const ;
63 static bool success( int ) ;
64 void setCredentials( int ) ;
65 void checkAccount( bool ) ;
66 void openSession() ;
67 void closeSession() ;
68 std::string name() const ;
69
70 public:
71 ~PamImp() ;
72 PamImp( const PamImp & ) = delete ;
73 PamImp( PamImp && ) = delete ;
74 PamImp & operator=( const PamImp & ) = delete ;
75 PamImp & operator=( PamImp && ) = delete ;
76
77 public:
78 Pam & m_pam ;
79 int m_magic ;
80 mutable int m_rc ; // required for pam_end()
81 Handle m_hpam ;
82 Conversation m_conv ;
83 bool m_silent ;
84
85 private:
86 using Error = Pam::Error ;
87 using ItemArray = Pam::ItemArray ;
88 static constexpr int MAGIC = 3456 ;
89
90 private:
91 static int converseCallback( int n , G_PAM_CONST struct pam_message ** in ,
92 struct pam_response ** out , void * vp ) ;
93 static void delayCallback( int , unsigned , void * ) ;
94 static std::string decodeStyle( int pam_style ) ;
95 static void release( struct pam_response * , std::size_t ) ;
96 static char * strdup_( const char * ) ;
97} ;
98
99// ==
100
101G::PamImp::PamImp( G::Pam & pam , const std::string & application , const std::string & user , bool silent ) :
102 m_pam(pam) ,
103 m_magic(MAGIC) ,
104 m_rc(PAM_SUCCESS) ,
105 m_hpam(nullptr) ,
106 m_conv{} ,
107 m_silent(silent)
108{
109 G_DEBUG( "G::PamImp::ctor: [" << application << "] [" << user << "]" ) ;
110
111 m_conv.conv = converseCallback ;
112 m_conv.appdata_ptr = this ;
113 m_rc = ::pam_start( application.c_str() , user.c_str() , &m_conv , &m_hpam ) ;
114 if( m_rc != PAM_SUCCESS )
115 {
116 throw Error( "pam_start" , m_rc ) ;
117 }
118
119 // (linux-specific)
120 #ifdef PAM_FAIL_DELAY
121 m_rc = ::pam_set_item( m_hpam , PAM_FAIL_DELAY , reinterpret_cast<const void*>(delayCallback) ) ;
122 if( m_rc != PAM_SUCCESS )
123 {
124 ::pam_end( m_hpam , m_rc ) ;
125 throw Error( "pam_set_item" , m_rc , ::pam_strerror(hpam(),m_rc) ) ;
126 }
127 #endif
128}
129
130G::PamImp::~PamImp()
131{
132 try
133 {
134 G_DEBUG( "G::PamImp::dtor" ) ;
135 ::pam_end( m_hpam , m_rc ) ;
136 m_magic = 0 ;
137 }
138 catch(...)
139 {
140 }
141}
142
143G::PamImp::Handle G::PamImp::hpam() const
144{
145 return m_hpam ;
146}
147
148 bool G::PamImp::silent() const
149{
150 return m_silent ;
151}
152
153std::string G::PamImp::decodeStyle( int pam_style )
154{
155 std::string defolt = std::string( "#" ) + Str::fromInt( pam_style ) ;
156 if( pam_style == PAM_PROMPT_ECHO_OFF ) return "password" ;
157 if( pam_style == PAM_PROMPT_ECHO_ON ) return "prompt" ;
158 if( pam_style == PAM_ERROR_MSG ) return "error" ;
159 if( pam_style == PAM_TEXT_INFO ) return "info" ;
160 return defolt ;
161}
162
163 bool G::PamImp::authenticate( bool require_token )
164{
165 int flags = 0 ;
166 if( silent() ) flags |= static_cast<int>(PAM_SILENT) ;
167 if( require_token ) flags |= static_cast<int>(PAM_DISALLOW_NULL_AUTHTOK) ;
168 m_rc = ::pam_authenticate( hpam() , flags ) ;
169 #ifdef PAM_INCOMPLETE
170 if( m_rc == PAM_INCOMPLETE )
171 return false ;
172 #endif
173
174 check( "pam_authenticate" , m_rc ) ;
175 return true ;
176}
177
178std::string G::PamImp::name() const
179{
180 G_PAM_CONST void * vp = nullptr ;
181 m_rc = ::pam_get_item( hpam() , PAM_USER , &vp ) ;
182 check( "pam_get_item" , m_rc ) ;
183 const char * cp = static_cast<const char*>(vp) ;
184 return std::string( cp ? cp : "" ) ;
185}
186
187 void G::PamImp::setCredentials( int flag )
188{
189 int flags = 0 ;
190 flags |= flag ;
191 if( silent() ) flags |= static_cast<int>(PAM_SILENT) ;
192 m_rc = ::pam_setcred( hpam() , flags ) ;
193 check( "pam_setcred" , m_rc ) ;
194}
195
196 void G::PamImp::checkAccount( bool require_token )
197{
198 int flags = 0 ;
199 if( silent() ) flags |= static_cast<int>(PAM_SILENT) ;
200 if( require_token ) flags |= static_cast<int>(PAM_DISALLOW_NULL_AUTHTOK) ;
201 m_rc = ::pam_acct_mgmt( hpam() , flags ) ;
202 check( "pam_acct_mgmt" , m_rc ) ;
203}
204
205 void G::PamImp::release( struct pam_response * rsp , std::size_t n )
206{
207 if( rsp != nullptr )
208 {
209 for( std::size_t i = 0U ; i < n ; i++ )
210 {
211 if( rsp[i].resp != nullptr )
212 std::free( rsp[i].resp ) ; // NOLINT
213 }
214 }
215 std::free( rsp ) ; // NOLINT
216}
217
218 int G::PamImp::converseCallback( int n_in , G_PAM_CONST struct pam_message ** in ,
219 struct pam_response ** out , void * vp )
220{
221 G_ASSERT( out != nullptr ) ;
222 if( n_in <= 0 )
223 {
224 G_ERROR( "G::Pam::converseCallback: invalid count" ) ;
225 return PAM_CONV_ERR ;
226 }
227 std::size_t n = static_cast<std::size_t>(n_in) ;
228
229 // pam_conv(3) on linux points out that the pam interface is under-specified, and on some
230 // systems, possibly including solaris, the "in" pointer is interpreted differently - this
231 // is only a problem for n greater than one, so warn about it at run-time
232 //
233 if( n > 1U )
234 {
235 G_WARNING_ONCE( "PamImp::converseCallback: received a complex pam converse() structure: "
236 "proceed with caution" ) ;
237 }
238
239 *out = nullptr ;
240 struct pam_response * rsp = nullptr ;
241 try
242 {
243 G_DEBUG( "G::Pam::converseCallback: called back from pam with " << n << " item(s)" ) ;
244 PamImp * This = static_cast<PamImp*>(vp) ;
245 G_ASSERT( This->m_magic == MAGIC ) ;
246
247 // convert the c items into a c++ container -- treat
248 // "in" as a pointer to a contiguous array of pointers
249 // (see linux man pam_conv)
250 //
251 ItemArray array( n ) ;
252 for( std::size_t i = 0U ; i < n ; i++ )
253 {
254 std::string & s1 = const_cast<std::string&>(array[i].in_type) ;
255 s1 = decodeStyle( in[i]->msg_style ) ;
256
257 std::string & s2 = const_cast<std::string&>(array[i].in) ;
258 s2 = std::string(in[i]->msg ? in[i]->msg : "") ;
259
260 array[i].out_defined = false ;
261 }
262
263 // do the conversation
264 //
265 This->m_pam.converse( array ) ;
266 G_ASSERT( array.size() == n ) ;
267
268 // allocate the response - treat "out" as a pointer to a pointer
269 // to a contiguous array of structures (see linux man pam_conv)
270 //
271 rsp = static_cast<struct pam_response*>( std::malloc(n*sizeof(struct pam_response)) ) ; // NOLINT
272 if( rsp == nullptr )
273 throw std::bad_alloc() ;
274 for( std::size_t j = 0U ; j < n ; j++ )
275 rsp[j].resp = nullptr ;
276
277 // fill in the response from the c++ container
278 //
279 for( std::size_t i = 0U ; i < n ; i++ )
280 {
281 rsp[i].resp_retcode = 0 ;
282 if( array[i].out_defined )
283 {
284 char * response = strdup_( array[i].out.c_str() ) ;
285 if( response == nullptr )
286 throw std::bad_alloc() ;
287 rsp[i].resp = response ;
288 }
289 }
290
291 *out = rsp ;
292 G_DEBUG( "G::Pam::converseCallback: returning to pam from callback" ) ;
293 return PAM_SUCCESS ;
294 }
295 catch(...) // c callback
296 {
297 G_ERROR( "G::Pam::converseCallback: exception" ) ;
298 release( rsp , n ) ;
299 return PAM_CONV_ERR ;
300 }
301}
302
303 void G::PamImp::delayCallback( int status , unsigned delay_usec , void * pam_vp )
304{
305 try
306 {
307 G_DEBUG( "G::Pam::delayCallback: status=" << status << ", delay=" << delay_usec ) ;
308 if( status != PAM_SUCCESS )
309 {
310 PamImp * This = static_cast<PamImp*>(pam_vp) ;
311 if( This != nullptr )
312 {
313 G_ASSERT( This->m_magic == MAGIC ) ;
314 This->m_pam.delay( delay_usec ) ;
315 }
316 }
317 }
318 catch(...) // c callback
319 {
320 G_ERROR( "G::Pam::delayCallback: exception" ) ;
321 }
322}
323
324 void G::PamImp::openSession()
325{
326 int flags = 0 ;
327 if( silent() ) flags |= static_cast<int>(PAM_SILENT) ;
328 m_rc = ::pam_open_session( hpam() , flags ) ;
329 check( "pam_open_session" , m_rc ) ;
330}
331
332 void G::PamImp::closeSession()
333{
334 int flags = 0 ;
335 if( silent() ) flags |= static_cast<int>(PAM_SILENT) ;
336 m_rc = ::pam_close_session( hpam() , flags ) ;
337 check( "pam_close_session" , m_rc ) ;
338}
339
340 bool G::PamImp::success( int rc )
341{
342 return rc == PAM_SUCCESS ;
343}
344
345 void G::PamImp::check( const std::string & op , int rc ) const
346{
347 if( !success(rc) )
348 throw Error( op , rc , ::pam_strerror(hpam(),rc) ) ;
349}
350
351 char * G::PamImp::strdup_( const char * p )
352{
353 p = p ? p : "" ;
354 char * copy = static_cast<char*>( std::malloc(std::strlen(p)+1U) ) ; // NOLINT
355 if( copy != nullptr )
356 std::strcpy( copy , p ) ; // NOLINT
357 return copy ;
358}
359
360// ==
361
362 G::Pam::Pam( const std::string & application , const std::string & user , bool silent ) :
363 m_imp(std::make_unique<PamImp>(*this,application,user,silent))
364{
365}
366
367 G::Pam::~Pam()
368= default;
369
370 bool G::Pam::authenticate( bool require_token )
371{
372 G_DEBUG( "G::Pam::authenticate" ) ;
373 return m_imp->authenticate( require_token ) ;
374}
375
376 void G::Pam::checkAccount( bool require_token )
377{
378 G_DEBUG( "G::Pam::checkAccount" ) ;
379 return m_imp->checkAccount( require_token ) ;
380}
381
382 void G::Pam::establishCredentials()
383{
384 G_DEBUG( "G::Pam::establishCredentials" ) ;
385 m_imp->setCredentials( PAM_ESTABLISH_CRED ) ;
386}
387
388 void G::Pam::openSession()
389{
390 G_DEBUG( "G::Pam::openSession" ) ;
391 m_imp->openSession() ;
392}
393
394 void G::Pam::closeSession()
395{
396 G_DEBUG( "G::Pam::closeSession" ) ;
397 m_imp->closeSession() ;
398}
399
400 void G::Pam::deleteCredentials()
401{
402 m_imp->setCredentials( PAM_DELETE_CRED ) ;
403}
404
405 void G::Pam::reinitialiseCredentials()
406{
407 m_imp->setCredentials( PAM_REINITIALIZE_CRED ) ;
408}
409
410 void G::Pam::refreshCredentials()
411{
412 m_imp->setCredentials( PAM_REFRESH_CRED ) ;
413}
414
415 void G::Pam::delay( unsigned int usec )
416{
417 // this is the default implementation, usually overridden
418 if( usec != 0U )
419 {
420 // (sys/select.h is included from gdef.h)
421 using Timeval = struct timeval ;
422 Timeval timeout ;
423 timeout.tv_sec = usec / 1000000U ;
424 timeout.tv_usec = usec % 1000000U ;
425 ::select( 0 , nullptr , nullptr , nullptr , &timeout ) ;
426 }
427}
428
429std::string G::Pam::name() const
430{
431 return m_imp->name() ;
432}
433
G::PamImp
A pimple-pattern implementation class for G::Pam.
Definition: gpam_linux.cpp:54
G::Pam::Error
An exception class for G::Pam.
Definition: gpam.h:69
G::Pam
A thin interface to the system PAM library, with two pure virtual methods that derived classes should...
Definition: gpam.h:59
G::Pam::deleteCredentials
void deleteCredentials()
Deletes credentials.
Definition: gpam_linux.cpp:400
G::Pam::checkAccount
void checkAccount(bool require_token)
Does "account management", checking that the authenticated user is currently allowed to use the syste...
Definition: gpam_linux.cpp:376
G::Pam::authenticate
bool authenticate(bool require_token)
Authenticates the user.
Definition: gpam_linux.cpp:370
G::Pam::delay
virtual void delay(unsigned int usec)=0
Called when the pam library wants the application to introduce a delay to prevent brute-force attacks...
Definition: gpam_linux.cpp:415
G::Pam::Pam
Pam(const std::string &app, const std::string &user, bool silent)
Constructor.
Definition: gpam_linux.cpp:362
G::Pam::refreshCredentials
void refreshCredentials()
Refreshes credentials.
Definition: gpam_linux.cpp:410
G::Pam::openSession
void openSession()
Starts a session.
Definition: gpam_linux.cpp:388
G::Pam::reinitialiseCredentials
void reinitialiseCredentials()
Reinitialises credentials.
Definition: gpam_linux.cpp:405
G::Pam::~Pam
virtual ~Pam()
Destructor.
G::Pam::name
std::string name() const
Returns the authenticated user name.
Definition: gpam_linux.cpp:429
G::Pam::closeSession
void closeSession()
Closes a session.
Definition: gpam_linux.cpp:394
G::Pam::establishCredentials
void establishCredentials()
Embues the authenticated user with their credentials, such as "tickets" in the form of environment va...
Definition: gpam_linux.cpp:382
G::Str::fromInt
static std::string fromInt(int i)
Converts int 'i' to a string.
Definition: gstr.h:561
gassert.h
gexception.h
glog.h
gpam.h
gstr.h