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