E-MailRelay

gpam_linux.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 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 {MAGIC} ;
80 mutable int m_rc {PAM_SUCCESS} ; // required for pam_end()
81 Handle m_hpam {nullptr} ;
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_conv{} ,
104 m_silent(silent)
105{
106 G_DEBUG( "G::PamImp::ctor: [" << application << "] [" << user << "]" ) ;
107
108 m_conv.conv = converseCallback ;
109 m_conv.appdata_ptr = this ;
110 m_rc = ::pam_start( application.c_str() , user.c_str() , &m_conv , &m_hpam ) ;
111 if( m_rc != PAM_SUCCESS )
112 {
113 throw Error( "pam_start" , m_rc ) ;
114 }
115
116 // (linux-specific)
117 #ifdef PAM_FAIL_DELAY
118 m_rc = ::pam_set_item( m_hpam , PAM_FAIL_DELAY , reinterpret_cast<const void*>(delayCallback) ) ;
119 if( m_rc != PAM_SUCCESS )
120 {
121 ::pam_end( m_hpam , m_rc ) ;
122 throw Error( "pam_set_item" , m_rc , ::pam_strerror(hpam(),m_rc) ) ;
123 }
124 #endif
125}
126
127G::PamImp::~PamImp()
128{
129 try
130 {
131 G_DEBUG( "G::PamImp::dtor" ) ;
132 ::pam_end( m_hpam , m_rc ) ;
133 m_magic = 0 ;
134 }
135 catch(...)
136 {
137 }
138}
139
140G::PamImp::Handle G::PamImp::hpam() const
141{
142 return m_hpam ;
143}
144
145 bool G::PamImp::silent() const
146{
147 return m_silent ;
148}
149
150std::string G::PamImp::decodeStyle( int pam_style )
151{
152 std::string defolt = std::string( "#" ) + Str::fromInt( pam_style ) ;
153 if( pam_style == PAM_PROMPT_ECHO_OFF ) return "password" ;
154 if( pam_style == PAM_PROMPT_ECHO_ON ) return "prompt" ;
155 if( pam_style == PAM_ERROR_MSG ) return "error" ;
156 if( pam_style == PAM_TEXT_INFO ) return "info" ;
157 return defolt ;
158}
159
160 bool G::PamImp::authenticate( bool require_token )
161{
162 int flags = 0 ;
163 if( silent() ) flags |= static_cast<int>(PAM_SILENT) ;
164 if( require_token ) flags |= static_cast<int>(PAM_DISALLOW_NULL_AUTHTOK) ;
165 m_rc = ::pam_authenticate( hpam() , flags ) ;
166 #ifdef PAM_INCOMPLETE
167 if( m_rc == PAM_INCOMPLETE )
168 return false ;
169 #endif
170
171 check( "pam_authenticate" , m_rc ) ;
172 return true ;
173}
174
175std::string G::PamImp::name() const
176{
177 G_PAM_CONST void * vp = nullptr ;
178 m_rc = ::pam_get_item( hpam() , PAM_USER , &vp ) ;
179 check( "pam_get_item" , m_rc ) ;
180 const char * cp = static_cast<const char*>(vp) ;
181 return { cp ? cp : "" } ;
182}
183
184 void G::PamImp::setCredentials( int flag )
185{
186 int flags = 0 ;
187 flags |= flag ;
188 if( silent() ) flags |= static_cast<int>(PAM_SILENT) ;
189 m_rc = ::pam_setcred( hpam() , flags ) ;
190 check( "pam_setcred" , m_rc ) ;
191}
192
193 void G::PamImp::checkAccount( bool require_token )
194{
195 int flags = 0 ;
196 if( silent() ) flags |= static_cast<int>(PAM_SILENT) ;
197 if( require_token ) flags |= static_cast<int>(PAM_DISALLOW_NULL_AUTHTOK) ;
198 m_rc = ::pam_acct_mgmt( hpam() , flags ) ;
199 check( "pam_acct_mgmt" , m_rc ) ;
200}
201
202 void G::PamImp::release( struct pam_response * rsp , std::size_t n )
203{
204 if( rsp != nullptr )
205 {
206 for( std::size_t i = 0U ; i < n ; i++ )
207 {
208 if( rsp[i].resp != nullptr )
209 std::free( rsp[i].resp ) ; // NOLINT
210 }
211 }
212 std::free( rsp ) ; // NOLINT
213}
214
215 int G::PamImp::converseCallback( int n_in , G_PAM_CONST struct pam_message ** in ,
216 struct pam_response ** out , void * vp )
217{
218 G_ASSERT( out != nullptr ) ;
219 if( n_in <= 0 )
220 {
221 G_ERROR( "G::Pam::converseCallback: invalid count" ) ;
222 return PAM_CONV_ERR ;
223 }
224 std::size_t n = static_cast<std::size_t>(n_in) ;
225
226 // pam_conv(3) on linux points out that the pam interface is under-specified, and on some
227 // systems, possibly including solaris, the "in" pointer is interpreted differently - this
228 // is only a problem for n greater than one, so warn about it at run-time
229 //
230 if( n > 1U )
231 {
232 G_WARNING_ONCE( "PamImp::converseCallback: received a complex pam converse() structure: "
233 "proceed with caution" ) ;
234 }
235
236 *out = nullptr ;
237 struct pam_response * rsp = nullptr ;
238 try
239 {
240 G_DEBUG( "G::Pam::converseCallback: called back from pam with " << n << " item(s)" ) ;
241 PamImp * This = static_cast<PamImp*>(vp) ;
242 G_ASSERT( This->m_magic == MAGIC ) ;
243
244 // convert the c items into a c++ container -- treat
245 // "in" as a pointer to a contiguous array of pointers
246 // (see linux man pam_conv)
247 //
248 ItemArray array( n ) ;
249 for( std::size_t i = 0U ; i < n ; i++ )
250 {
251 array[i].in_type = decodeStyle( in[i]->msg_style ) ;
252 array[i].in = std::string( in[i]->msg ? in[i]->msg : "" ) ;
253 array[i].out_defined = false ;
254 }
255
256 // do the conversation
257 //
258 This->m_pam.converse( array ) ;
259 G_ASSERT( array.size() == n ) ;
260
261 // allocate the response - treat "out" as a pointer to a pointer
262 // to a contiguous array of structures (see linux man pam_conv)
263 //
264 rsp = static_cast<struct pam_response*>( std::malloc(n*sizeof(struct pam_response)) ) ; // NOLINT
265 if( rsp == nullptr )
266 throw std::bad_alloc() ;
267 for( std::size_t j = 0U ; j < n ; j++ )
268 rsp[j].resp = nullptr ;
269
270 // fill in the response from the c++ container
271 //
272 for( std::size_t i = 0U ; i < n ; i++ )
273 {
274 rsp[i].resp_retcode = 0 ;
275 if( array[i].out_defined )
276 {
277 char * response = strdup_( array[i].out.c_str() ) ;
278 if( response == nullptr )
279 throw std::bad_alloc() ;
280 rsp[i].resp = response ;
281 }
282 }
283
284 *out = rsp ;
285 G_DEBUG( "G::Pam::converseCallback: returning to pam from callback" ) ;
286 return PAM_SUCCESS ;
287 }
288 catch(...) // c callback
289 {
290 G_ERROR( "G::Pam::converseCallback: exception" ) ;
291 release( rsp , n ) ;
292 return PAM_CONV_ERR ;
293 }
294}
295
296 void G::PamImp::delayCallback( int status , unsigned delay_usec , void * pam_vp )
297{
298 try
299 {
300 G_DEBUG( "G::Pam::delayCallback: status=" << status << ", delay=" << delay_usec ) ;
301 if( status != PAM_SUCCESS )
302 {
303 PamImp * This = static_cast<PamImp*>(pam_vp) ;
304 if( This != nullptr )
305 {
306 G_ASSERT( This->m_magic == MAGIC ) ;
307 This->m_pam.delay( delay_usec ) ;
308 }
309 }
310 }
311 catch(...) // c callback
312 {
313 G_ERROR( "G::Pam::delayCallback: exception" ) ;
314 }
315}
316
317 void G::PamImp::openSession()
318{
319 int flags = 0 ;
320 if( silent() ) flags |= static_cast<int>(PAM_SILENT) ;
321 m_rc = ::pam_open_session( hpam() , flags ) ;
322 check( "pam_open_session" , m_rc ) ;
323}
324
325 void G::PamImp::closeSession()
326{
327 int flags = 0 ;
328 if( silent() ) flags |= static_cast<int>(PAM_SILENT) ;
329 m_rc = ::pam_close_session( hpam() , flags ) ;
330 check( "pam_close_session" , m_rc ) ;
331}
332
333 bool G::PamImp::success( int rc )
334{
335 return rc == PAM_SUCCESS ;
336}
337
338 void G::PamImp::check( const std::string & op , int rc ) const
339{
340 if( !success(rc) )
341 throw Error( op , rc , ::pam_strerror(hpam(),rc) ) ;
342}
343
344 char * G::PamImp::strdup_( const char * p )
345{
346 p = p ? p : "" ;
347 char * copy = static_cast<char*>( std::malloc(std::strlen(p)+1U) ) ; // NOLINT
348 if( copy != nullptr )
349 std::strcpy( copy , p ) ; // NOLINT
350 return copy ;
351}
352
353// ==
354
355 G::Pam::Pam( const std::string & application , const std::string & user , bool silent ) :
356 m_imp(std::make_unique<PamImp>(*this,application,user,silent))
357{
358}
359
360 G::Pam::~Pam()
361= default;
362
363 bool G::Pam::authenticate( bool require_token )
364{
365 G_DEBUG( "G::Pam::authenticate" ) ;
366 return m_imp->authenticate( require_token ) ;
367}
368
369#ifndef G_LIB_SMALL
370 void G::Pam::checkAccount( bool require_token )
371{
372 G_DEBUG( "G::Pam::checkAccount" ) ;
373 m_imp->checkAccount( require_token ) ;
374}
375#endif
376
377#ifndef G_LIB_SMALL
378 void G::Pam::establishCredentials()
379{
380 G_DEBUG( "G::Pam::establishCredentials" ) ;
381 m_imp->setCredentials( PAM_ESTABLISH_CRED ) ;
382}
383#endif
384
385#ifndef G_LIB_SMALL
386 void G::Pam::openSession()
387{
388 G_DEBUG( "G::Pam::openSession" ) ;
389 m_imp->openSession() ;
390}
391#endif
392
393#ifndef G_LIB_SMALL
394 void G::Pam::closeSession()
395{
396 G_DEBUG( "G::Pam::closeSession" ) ;
397 m_imp->closeSession() ;
398}
399#endif
400
401#ifndef G_LIB_SMALL
402 void G::Pam::deleteCredentials()
403{
404 m_imp->setCredentials( PAM_DELETE_CRED ) ;
405}
406#endif
407
408#ifndef G_LIB_SMALL
409 void G::Pam::reinitialiseCredentials()
410{
411 m_imp->setCredentials( PAM_REINITIALIZE_CRED ) ;
412}
413#endif
414
415#ifndef G_LIB_SMALL
416 void G::Pam::refreshCredentials()
417{
418 m_imp->setCredentials( PAM_REFRESH_CRED ) ;
419}
420#endif
421
422#ifndef G_LIB_SMALL
423 void G::Pam::delay( unsigned int usec )
424{
425 // this is the default implementation, usually overridden
426 if( usec != 0U )
427 {
428 // (sys/select.h is included from gdef.h)
429 using Timeval = struct timeval ;
430 Timeval timeout ;
431 timeout.tv_sec = usec / 1000000U ;
432 timeout.tv_usec = usec % 1000000U ;
433 ::select( 0 , nullptr , nullptr , nullptr , &timeout ) ;
434 }
435}
436#endif
437
438#ifndef G_LIB_SMALL
439std::string G::Pam::name() const
440{
441 return m_imp->name() ;
442}
443#endif
444
G::Pam
A thin interface to the system PAM library, with two pure virtual methods that derived classes should...
Definition: gpam.h:60
G::Pam::deleteCredentials
void deleteCredentials()
Deletes credentials.
Definition: gpam_linux.cpp:402
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:370
G::Pam::authenticate
bool authenticate(bool require_token)
Authenticates the user.
Definition: gpam_linux.cpp:363
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:423
G::Pam::Pam
Pam(const std::string &app, const std::string &user, bool silent)
Constructor.
Definition: gpam_linux.cpp:355
G::Pam::refreshCredentials
void refreshCredentials()
Refreshes credentials.
Definition: gpam_linux.cpp:416
G::Pam::openSession
void openSession()
Starts a session.
Definition: gpam_linux.cpp:386
G::Pam::reinitialiseCredentials
void reinitialiseCredentials()
Reinitialises credentials.
Definition: gpam_linux.cpp:409
G::Pam::~Pam
virtual ~Pam()
Destructor.
G::Pam::name
std::string name() const
Returns the authenticated user name.
Definition: gpam_linux.cpp:439
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:378
gassert.h
gexception.h
glog.h
gpam.h
gstr.h
std
STL namespace.