2000-05-09 15:43:00 +04:00
/* pam_winbind module
Copyright Andrew Tridgell < tridge @ samba . org > 2000
2002-01-30 06:14:20 +03:00
Copyright Tim Potter < tpot @ samba . org > 2000
2002-03-13 04:26:59 +03:00
Copyright Andrew Bartlett < abartlet @ samba . org > 2002
2006-02-04 01:19:41 +03:00
Copyright Guenther Deschner < gd @ samba . org > 2005 - 2006
2000-05-09 15:43:00 +04:00
2005-02-04 03:25:33 +03:00
largely based on pam_userdb by Cristian Gafton < gafton @ redhat . com >
2002-02-05 12:40:36 +03:00
also contains large slabs of code from pam_unix by Elliot Lee < sopwith @ redhat . com >
( see copyright below for full details )
2000-05-09 15:43:00 +04:00
*/
2001-05-04 04:43:20 +04:00
# include "pam_winbind.h"
2000-05-09 15:43:00 +04:00
2002-02-05 12:40:36 +03:00
/* data tokens */
# define MAX_PASSWD_TRIES 3
2000-05-09 15:43:00 +04:00
/* some syslogging */
static void _pam_log ( int err , const char * format , . . . )
{
va_list args ;
va_start ( args , format ) ;
openlog ( MODULE_NAME , LOG_CONS | LOG_PID , LOG_AUTH ) ;
vsyslog ( err , format , args ) ;
va_end ( args ) ;
closelog ( ) ;
}
2006-02-04 01:19:41 +03:00
static void _pam_log_debug ( int ctrl , int err , const char * format , . . . )
{
va_list args ;
if ( ! ( ctrl & WINBIND_DEBUG_ARG ) ) {
return ;
}
va_start ( args , format ) ;
openlog ( MODULE_NAME , LOG_CONS | LOG_PID , LOG_AUTH ) ;
vsyslog ( err , format , args ) ;
va_end ( args ) ;
closelog ( ) ;
}
2006-04-11 18:40:53 +04:00
static int _pam_parse ( int argc , const char * * argv , dictionary * * d )
2000-05-09 15:43:00 +04:00
{
2006-02-04 01:19:41 +03:00
int ctrl = 0 ;
2006-04-11 18:40:53 +04:00
const char * config_file = NULL ;
2006-07-13 20:31:26 +04:00
int i ;
const char * * v ;
2006-02-04 01:19:41 +03:00
2006-05-31 19:45:19 +04:00
if ( d = = NULL ) {
2006-04-11 18:40:53 +04:00
goto config_from_pam ;
}
2006-02-04 01:19:41 +03:00
2006-07-13 20:31:26 +04:00
for ( i = argc , v = argv ; i - - > 0 ; + + v ) {
if ( ! strncasecmp ( * v , " config " , strlen ( " config " ) ) ) {
2006-04-11 18:40:53 +04:00
ctrl | = WINBIND_CONFIG_FILE ;
2006-07-13 20:31:26 +04:00
config_file = v [ i ] ;
2006-04-11 18:40:53 +04:00
break ;
}
}
if ( config_file = = NULL ) {
2006-04-11 19:18:46 +04:00
config_file = PAM_WINBIND_CONFIG_FILE ;
2006-04-11 18:40:53 +04:00
}
* d = iniparser_load ( CONST_DISCARD ( char * , config_file ) ) ;
if ( * d = = NULL ) {
2006-05-11 01:12:10 +04:00
goto config_from_pam ;
2006-02-04 01:19:41 +03:00
}
2006-04-11 19:18:46 +04:00
if ( iniparser_getboolean ( * d , CONST_DISCARD ( char * , " global:debug " ) , False ) ) {
2006-04-11 18:40:53 +04:00
ctrl | = WINBIND_DEBUG_ARG ;
}
2006-04-11 19:18:46 +04:00
if ( iniparser_getboolean ( * d , CONST_DISCARD ( char * , " global:cached_login " ) , False ) ) {
2006-02-04 01:19:41 +03:00
ctrl | = WINBIND_CACHED_LOGIN ;
}
2006-04-11 18:40:53 +04:00
2006-04-11 19:18:46 +04:00
if ( iniparser_getboolean ( * d , CONST_DISCARD ( char * , " global:krb5_auth " ) , False ) ) {
2006-02-04 01:19:41 +03:00
ctrl | = WINBIND_KRB5_AUTH ;
}
2006-04-11 18:40:53 +04:00
2006-04-11 19:18:46 +04:00
if ( iniparser_getstr ( * d , CONST_DISCARD ( char * , " global:krb5_ccache_type " ) ) ! = NULL ) {
2006-02-04 01:19:41 +03:00
ctrl | = WINBIND_KRB5_CCACHE_TYPE ;
}
2006-04-11 18:40:53 +04:00
2006-04-11 19:18:46 +04:00
if ( ( iniparser_getstr ( * d , CONST_DISCARD ( char * , " global:require-membership-of " ) ) ! = NULL ) | |
( iniparser_getstr ( * d , CONST_DISCARD ( char * , " global:require_membership_of " ) ) ! = NULL ) ) {
2006-02-04 01:19:41 +03:00
ctrl | = WINBIND_REQUIRED_MEMBERSHIP ;
}
2006-04-11 18:40:53 +04:00
config_from_pam :
2002-02-05 12:40:36 +03:00
/* step through arguments */
2006-07-13 20:31:26 +04:00
for ( i = argc , v = argv ; i - - > 0 ; + + v ) {
2002-02-05 12:40:36 +03:00
/* generic options */
2006-07-13 20:31:26 +04:00
if ( ! strcmp ( * v , " debug " ) )
2002-02-05 12:40:36 +03:00
ctrl | = WINBIND_DEBUG_ARG ;
2006-07-13 20:31:26 +04:00
else if ( ! strcasecmp ( * v , " use_authtok " ) )
2002-02-05 12:40:36 +03:00
ctrl | = WINBIND_USE_AUTHTOK_ARG ;
2006-07-13 20:31:26 +04:00
else if ( ! strcasecmp ( * v , " use_first_pass " ) )
2002-02-05 12:40:36 +03:00
ctrl | = WINBIND_USE_FIRST_PASS_ARG ;
2006-07-13 20:31:26 +04:00
else if ( ! strcasecmp ( * v , " try_first_pass " ) )
2002-03-23 11:28:19 +03:00
ctrl | = WINBIND_TRY_FIRST_PASS_ARG ;
2006-07-13 20:31:26 +04:00
else if ( ! strcasecmp ( * v , " unknown_ok " ) )
2002-02-05 12:40:36 +03:00
ctrl | = WINBIND_UNKNOWN_OK_ARG ;
2006-07-13 20:31:26 +04:00
else if ( ! strncasecmp ( * v , " require_membership_of " , strlen ( " require_membership_of " ) ) )
2004-10-01 07:28:39 +04:00
ctrl | = WINBIND_REQUIRED_MEMBERSHIP ;
2006-07-13 20:31:26 +04:00
else if ( ! strncasecmp ( * v , " require-membership-of " , strlen ( " require-membership-of " ) ) )
2004-08-18 20:25:41 +04:00
ctrl | = WINBIND_REQUIRED_MEMBERSHIP ;
2006-07-13 20:31:26 +04:00
else if ( ! strcasecmp ( * v , " krb5_auth " ) )
2006-02-04 01:19:41 +03:00
ctrl | = WINBIND_KRB5_AUTH ;
2006-07-13 20:31:26 +04:00
else if ( ! strncasecmp ( * v , " krb5_ccache_type " , strlen ( " krb5_ccache_type " ) ) )
2006-02-04 01:19:41 +03:00
ctrl | = WINBIND_KRB5_CCACHE_TYPE ;
2006-07-13 20:31:26 +04:00
else if ( ! strcasecmp ( * v , " cached_login " ) )
2006-02-04 01:19:41 +03:00
ctrl | = WINBIND_CACHED_LOGIN ;
2002-02-05 12:40:36 +03:00
else {
2006-07-13 20:31:26 +04:00
_pam_log ( LOG_ERR , " pam_parse: unknown option; %s " , * v ) ;
2002-02-05 12:40:36 +03:00
}
2006-02-04 01:19:41 +03:00
2002-02-05 12:40:36 +03:00
}
return ctrl ;
2006-02-04 01:19:41 +03:00
} ;
2002-02-05 12:40:36 +03:00
2006-01-13 14:11:23 +03:00
static void _pam_winbind_cleanup_func ( pam_handle_t * pamh , void * data , int error_status )
{
SAFE_FREE ( data ) ;
}
2006-07-11 22:01:26 +04:00
/*
* Work around the pam API that has functions with void * * as parameters .
* These lead to strict aliasing warnings with gcc .
*/
static int _pam_get_item ( const pam_handle_t * pamh , int item_type ,
const void * _item )
{
const void * * item = ( const void * * ) _item ;
return pam_get_item ( pamh , item_type , item ) ;
}
static int _pam_get_data ( const pam_handle_t * pamh ,
const char * module_data_name , const void * _data )
{
const void * * data = ( const void * * ) _data ;
return pam_get_data ( pamh , module_data_name , data ) ;
}
2006-02-04 01:19:41 +03:00
static const struct ntstatus_errors {
const char * ntstatus_string ;
const char * error_string ;
} ntstatus_errors [ ] = {
{ " NT_STATUS_OK " , " Success " } ,
{ " NT_STATUS_BACKUP_CONTROLLER " , " No primary Domain Controler available " } ,
{ " NT_STATUS_PWD_TOO_SHORT " , " Password too short " } ,
{ " NT_STATUS_PWD_TOO_RECENT " , " The password of this user is too recent to change " } ,
{ " NT_STATUS_PWD_HISTORY_CONFLICT " , " Password is already in password history " } ,
{ " NT_STATUS_PASSWORD_EXPIRED " , " Your password has expired " } ,
{ " NT_STATUS_PASSWORD_MUST_CHANGE " , " You need to change your password now " } ,
{ " NT_STATUS_INVALID_WORKSTATION " , " You are not allowed to logon from this workstation " } ,
{ " NT_STATUS_INVALID_LOGON_HOURS " , " You are not allowed to logon at this time " } ,
{ " NT_STATUS_ACCOUNT_EXPIRED " , " Your account has expired. Please contact your System administrator " } , /* SCNR */
{ " NT_STATUS_ACCOUNT_DISABLED " , " Your account is disabled. Please contact your System administrator " } , /* SCNR */
{ " NT_STATUS_ACCOUNT_LOCKED_OUT " , " Your account has been locked. Please contact your System administrator " } , /* SCNR */
{ " NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT " , " Invalid Trust Account " } ,
{ " NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT " , " Invalid Trust Account " } ,
{ " NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT " , " Invalid Trust Account " } ,
{ " NT_STATUS_ACCESS_DENIED " , " Access is denied " } ,
{ NULL , NULL }
} ;
const char * _get_ntstatus_error_string ( const char * nt_status_string )
{
int i ;
for ( i = 0 ; ntstatus_errors [ i ] . ntstatus_string ! = NULL ; i + + ) {
2006-04-11 18:40:53 +04:00
if ( ! strcasecmp ( ntstatus_errors [ i ] . ntstatus_string , nt_status_string ) ) {
2006-02-04 01:19:41 +03:00
return ntstatus_errors [ i ] . error_string ;
}
}
return NULL ;
}
2002-02-05 12:40:36 +03:00
/* --- authentication management functions --- */
/* Attempt a conversation */
2000-05-09 15:43:00 +04:00
2002-02-05 12:40:36 +03:00
static int converse ( pam_handle_t * pamh , int nargs ,
struct pam_message * * message ,
struct pam_response * * response )
{
2006-02-04 01:19:41 +03:00
int retval ;
struct pam_conv * conv ;
2006-07-11 22:01:26 +04:00
retval = _pam_get_item ( pamh , PAM_CONV , & conv ) ;
2006-02-04 01:19:41 +03:00
if ( retval = = PAM_SUCCESS ) {
retval = conv - > conv ( nargs , ( const struct pam_message * * ) message ,
response , conv - > appdata_ptr ) ;
}
2002-02-05 12:40:36 +03:00
2006-02-04 01:19:41 +03:00
return retval ; /* propagate error status */
2002-02-05 12:40:36 +03:00
}
2002-08-17 21:00:51 +04:00
static int _make_remark ( pam_handle_t * pamh , int type , const char * text )
2002-02-05 12:40:36 +03:00
{
int retval = PAM_SUCCESS ;
struct pam_message * pmsg [ 1 ] , msg [ 1 ] ;
struct pam_response * resp ;
pmsg [ 0 ] = & msg [ 0 ] ;
2006-05-07 00:12:42 +04:00
msg [ 0 ] . msg = CONST_DISCARD ( char * , text ) ;
2002-02-05 12:40:36 +03:00
msg [ 0 ] . msg_style = type ;
resp = NULL ;
retval = converse ( pamh , 1 , pmsg , & resp ) ;
if ( resp ) {
_pam_drop_reply ( resp , 1 ) ;
}
return retval ;
2000-05-09 15:43:00 +04:00
}
2006-02-04 01:19:41 +03:00
static int _make_remark_format ( pam_handle_t * pamh , int type , const char * format , . . . )
{
va_list args ;
char * var ;
2006-08-01 19:17:18 +04:00
int ret ;
2006-02-04 01:19:41 +03:00
va_start ( args , format ) ;
vasprintf ( & var , format , args ) ;
va_end ( args ) ;
2006-08-01 19:17:18 +04:00
ret = _make_remark ( pamh , type , var ) ;
SAFE_FREE ( var ) ;
return ret ;
2006-02-04 01:19:41 +03:00
}
static int pam_winbind_request ( pam_handle_t * pamh , int ctrl ,
enum winbindd_cmd req_type ,
2002-09-25 19:19:00 +04:00
struct winbindd_request * request ,
struct winbindd_response * response )
2000-05-09 15:43:00 +04:00
{
/* Fill in request and send down pipe */
2001-05-04 04:43:20 +04:00
init_request ( request , req_type ) ;
2000-05-09 15:43:00 +04:00
2005-09-19 22:49:18 +04:00
if ( write_sock ( request , sizeof ( * request ) , 0 ) = = - 1 ) {
2002-02-05 12:40:36 +03:00
_pam_log ( LOG_ERR , " write to socket failed! " ) ;
2002-09-25 19:19:00 +04:00
close_sock ( ) ;
2002-02-05 12:40:36 +03:00
return PAM_SERVICE_ERR ;
2000-05-09 15:43:00 +04:00
}
/* Wait for reply */
2001-05-04 04:43:20 +04:00
if ( read_reply ( response ) = = - 1 ) {
2002-02-05 12:40:36 +03:00
_pam_log ( LOG_ERR , " read from socket failed! " ) ;
2002-09-25 19:19:00 +04:00
close_sock ( ) ;
2002-02-05 12:40:36 +03:00
return PAM_SERVICE_ERR ;
2000-05-09 15:43:00 +04:00
}
2002-09-25 19:19:00 +04:00
/* We are done with the socket - close it and avoid mischeif */
close_sock ( ) ;
2000-05-09 15:43:00 +04:00
/* Copy reply data from socket */
2001-05-04 04:43:20 +04:00
if ( response - > result ! = WINBINDD_OK ) {
2002-02-05 12:40:36 +03:00
if ( response - > data . auth . pam_error ! = PAM_SUCCESS ) {
2006-05-03 19:19:31 +04:00
_pam_log ( LOG_ERR , " request failed: %s, PAM error was %s (%d), NT error was %s " ,
2003-01-15 21:57:41 +03:00
response - > data . auth . error_string ,
2006-05-03 19:19:31 +04:00
pam_strerror ( pamh , response - > data . auth . pam_error ) ,
2002-02-05 12:40:36 +03:00
response - > data . auth . pam_error ,
response - > data . auth . nt_status_string ) ;
return response - > data . auth . pam_error ;
} else {
_pam_log ( LOG_ERR , " request failed, but PAM error 0! " ) ;
return PAM_SERVICE_ERR ;
}
2000-05-09 15:43:00 +04:00
}
2006-02-04 01:19:41 +03:00
2002-02-05 12:40:36 +03:00
return PAM_SUCCESS ;
2000-05-09 15:43:00 +04:00
}
2006-02-04 01:19:41 +03:00
static int pam_winbind_request_log ( pam_handle_t * pamh ,
2002-10-26 06:20:59 +04:00
int ctrl ,
2006-02-04 01:19:41 +03:00
enum winbindd_cmd req_type ,
struct winbindd_request * request ,
struct winbindd_response * response ,
2002-10-26 06:20:59 +04:00
const char * user )
2001-05-04 04:43:20 +04:00
{
2002-02-05 12:40:36 +03:00
int retval ;
2001-05-04 04:43:20 +04:00
2006-02-04 01:19:41 +03:00
retval = pam_winbind_request ( pamh , ctrl , req_type , request , response ) ;
2002-02-05 12:40:36 +03:00
switch ( retval ) {
case PAM_AUTH_ERR :
/* incorrect password */
2004-08-18 20:25:41 +04:00
_pam_log ( LOG_WARNING , " user `%s' denied access (incorrect password or invalid membership) " , user ) ;
2002-02-05 12:40:36 +03:00
return retval ;
2002-07-15 14:35:28 +04:00
case PAM_ACCT_EXPIRED :
/* account expired */
_pam_log ( LOG_WARNING , " user `%s' account expired " , user ) ;
return retval ;
case PAM_AUTHTOK_EXPIRED :
/* password expired */
_pam_log ( LOG_WARNING , " user `%s' password expired " , user ) ;
return retval ;
2002-08-17 21:00:51 +04:00
case PAM_NEW_AUTHTOK_REQD :
2006-02-04 01:19:41 +03:00
/* new password required */
2002-08-17 21:00:51 +04:00
_pam_log ( LOG_WARNING , " user `%s' new password required " , user ) ;
return retval ;
2002-02-05 12:40:36 +03:00
case PAM_USER_UNKNOWN :
/* the user does not exist */
2006-05-03 14:19:49 +04:00
_pam_log_debug ( ctrl , LOG_NOTICE , " user `%s' not found " , user ) ;
2002-02-05 12:40:36 +03:00
if ( ctrl & WINBIND_UNKNOWN_OK_ARG ) {
return PAM_IGNORE ;
}
return retval ;
case PAM_SUCCESS :
2002-10-26 06:20:59 +04:00
if ( req_type = = WINBINDD_PAM_AUTH ) {
/* Otherwise, the authentication looked good */
2004-09-22 04:50:12 +04:00
_pam_log ( LOG_NOTICE , " user '%s' granted access " , user ) ;
2002-10-26 06:20:59 +04:00
} else if ( req_type = = WINBINDD_PAM_CHAUTHTOK ) {
/* Otherwise, the authentication looked good */
_pam_log ( LOG_NOTICE , " user '%s' password changed " , user ) ;
} else {
/* Otherwise, the authentication looked good */
_pam_log ( LOG_NOTICE , " user '%s' OK " , user ) ;
}
2006-02-04 01:19:41 +03:00
2002-02-05 12:40:36 +03:00
return retval ;
default :
/* we don't know anything about this return value */
2004-11-22 17:34:45 +03:00
_pam_log ( LOG_ERR , " internal module error (retval = %d, user = `%s') " ,
2002-02-05 12:40:36 +03:00
retval , user ) ;
return retval ;
}
2002-10-26 06:20:59 +04:00
}
/* talk to winbindd */
2006-02-04 01:19:41 +03:00
static int winbind_auth_request ( pam_handle_t * pamh ,
int ctrl ,
const char * user ,
const char * pass ,
const char * member ,
const char * cctype ,
2006-05-02 23:22:39 +04:00
int process_result ,
2006-09-05 09:28:31 +04:00
time_t * pwd_last_set ,
char * * user_ret )
2002-10-26 06:20:59 +04:00
{
struct winbindd_request request ;
struct winbindd_response response ;
2006-02-04 01:19:41 +03:00
int ret ;
2002-10-26 06:20:59 +04:00
ZERO_STRUCT ( request ) ;
2006-02-04 01:19:41 +03:00
ZERO_STRUCT ( response ) ;
2002-10-26 06:20:59 +04:00
2006-05-02 23:22:39 +04:00
if ( pwd_last_set ) {
* pwd_last_set = 0 ;
}
2002-10-26 06:20:59 +04:00
strncpy ( request . data . auth . user , user ,
2006-02-04 01:19:41 +03:00
sizeof ( request . data . auth . user ) - 1 ) ;
2002-10-26 06:20:59 +04:00
strncpy ( request . data . auth . pass , pass ,
2006-02-04 01:19:41 +03:00
sizeof ( request . data . auth . pass ) - 1 ) ;
request . data . auth . krb5_cc_type [ 0 ] = ' \0 ' ;
request . data . auth . uid = - 1 ;
2006-08-23 02:53:08 +04:00
request . flags = WBFLAG_PAM_INFO3_TEXT | WBFLAG_PAM_CONTACT_TRUSTDOM ;
2006-02-04 01:19:41 +03:00
if ( ctrl & WINBIND_KRB5_AUTH ) {
struct passwd * pwd = NULL ;
_pam_log_debug ( ctrl , LOG_DEBUG , " enabling krb5 login flag \n " ) ;
request . flags | = WBFLAG_PAM_KRB5 | WBFLAG_PAM_FALLBACK_AFTER_KRB5 ;
pwd = getpwnam ( user ) ;
if ( pwd = = NULL ) {
return PAM_USER_UNKNOWN ;
}
request . data . auth . uid = pwd - > pw_uid ;
}
2004-08-18 20:25:41 +04:00
2006-02-04 01:19:41 +03:00
if ( ctrl & WINBIND_CACHED_LOGIN ) {
_pam_log_debug ( ctrl , LOG_DEBUG , " enabling cached login flag \n " ) ;
request . flags | = WBFLAG_PAM_CACHED_LOGIN ;
}
2006-09-05 09:28:31 +04:00
if ( user_ret ) {
* user_ret = NULL ;
request . flags | = WBFLAG_PAM_UNIX_NAME ;
}
2006-02-04 01:19:41 +03:00
if ( cctype ! = NULL ) {
strncpy ( request . data . auth . krb5_cc_type , cctype ,
sizeof ( request . data . auth . krb5_cc_type ) - 1 ) ;
_pam_log_debug ( ctrl , LOG_DEBUG , " enabling request for a %s krb5 ccache \n " , cctype ) ;
}
request . data . auth . require_membership_of_sid [ 0 ] = ' \0 ' ;
if ( member ! = NULL ) {
strncpy ( request . data . auth . require_membership_of_sid , member ,
sizeof ( request . data . auth . require_membership_of_sid ) - 1 ) ;
}
2004-08-18 20:25:41 +04:00
/* lookup name? */
2006-02-04 01:19:41 +03:00
if ( ( member ! = NULL ) & & ( strncmp ( " S- " , member , 2 ) ! = 0 ) ) {
2004-08-18 20:25:41 +04:00
2004-10-01 07:28:39 +04:00
struct winbindd_request sid_request ;
struct winbindd_response sid_response ;
2004-08-18 20:25:41 +04:00
2004-10-02 15:11:49 +04:00
ZERO_STRUCT ( sid_request ) ;
ZERO_STRUCT ( sid_response ) ;
2004-08-18 20:25:41 +04:00
2006-02-04 01:19:41 +03:00
_pam_log_debug ( ctrl , LOG_DEBUG , " no sid given, looking up: %s \n " , member ) ;
2004-08-18 20:25:41 +04:00
/* fortunatly winbindd can handle non-separated names */
2006-04-11 18:40:53 +04:00
strncpy ( sid_request . data . name . name , member ,
sizeof ( sid_request . data . name . name ) - 1 ) ;
2004-08-18 20:25:41 +04:00
2006-02-04 01:19:41 +03:00
if ( pam_winbind_request_log ( pamh , ctrl , WINBINDD_LOOKUPNAME , & sid_request , & sid_response , user ) ) {
2004-08-18 20:25:41 +04:00
_pam_log ( LOG_INFO , " could not lookup name: %s \n " , member ) ;
return PAM_AUTH_ERR ;
}
2004-10-02 15:11:49 +04:00
member = sid_response . data . sid . sid ;
2006-02-04 01:19:41 +03:00
strncpy ( request . data . auth . require_membership_of_sid , member ,
sizeof ( request . data . auth . require_membership_of_sid ) - 1 ) ;
2004-08-18 20:25:41 +04:00
}
2006-02-04 01:19:41 +03:00
ret = pam_winbind_request_log ( pamh , ctrl , WINBINDD_PAM_AUTH , & request , & response , user ) ;
2004-08-18 20:25:41 +04:00
2006-05-02 23:22:39 +04:00
if ( pwd_last_set ) {
* pwd_last_set = response . data . auth . info3 . pass_last_set_time ;
}
2006-02-04 01:19:41 +03:00
if ( ( ctrl & WINBIND_KRB5_AUTH ) & &
response . data . auth . krb5ccname [ 0 ] ! = ' \0 ' ) {
char var [ PATH_MAX ] ;
_pam_log_debug ( ctrl , LOG_DEBUG , " request returned KRB5CCNAME: %s " ,
response . data . auth . krb5ccname ) ;
2002-10-26 06:20:59 +04:00
2006-02-04 01:19:41 +03:00
snprintf ( var , sizeof ( var ) , " KRB5CCNAME=%s " , response . data . auth . krb5ccname ) ;
ret = pam_putenv ( pamh , var ) ;
if ( ret ! = PAM_SUCCESS ) {
_pam_log ( LOG_ERR , " failed to set KRB5CCNAME to %s " , var ) ;
return ret ;
}
}
if ( ! process_result ) {
return ret ;
}
if ( ret ) {
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_PASSWORD_EXPIRED " ) ;
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_PASSWORD_MUST_CHANGE " ) ;
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_INVALID_WORKSTATION " ) ;
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_INVALID_LOGON_HOURS " ) ;
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_ACCOUNT_EXPIRED " ) ;
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_ACCOUNT_DISABLED " ) ;
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_ACCOUNT_LOCKED_OUT " ) ;
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT " ) ;
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT " ) ;
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT " ) ;
}
/* handle the case where the auth was ok, but the password must expire right now */
/* good catch from Ralf Haferkamp: an expiry of "never" is translated to -1 */
2006-02-08 14:57:38 +03:00
if ( ! ( response . data . auth . info3 . acct_flags & ACB_PWNOEXP ) & &
2006-02-07 20:18:29 +03:00
( response . data . auth . policy . expire > 0 ) & &
2006-02-04 01:19:41 +03:00
( response . data . auth . info3 . pass_last_set_time + response . data . auth . policy . expire < time ( NULL ) ) ) {
ret = PAM_AUTHTOK_EXPIRED ;
_pam_log_debug ( ctrl , LOG_DEBUG , " Password has expired (Password was last set: %d, "
" the policy says it should expire here %d (now it's: %d) \n " ,
response . data . auth . info3 . pass_last_set_time ,
response . data . auth . info3 . pass_last_set_time + response . data . auth . policy . expire ,
time ( NULL ) ) ;
PAM_WB_REMARK_DIRECT_RET ( pamh , " NT_STATUS_PASSWORD_EXPIRED " ) ;
}
/* warn a user if the password is about to expire soon */
2006-02-08 14:57:38 +03:00
if ( ! ( response . data . auth . info3 . acct_flags & ACB_PWNOEXP ) & &
2006-02-07 20:18:29 +03:00
( response . data . auth . policy . expire ) & &
2006-02-04 01:19:41 +03:00
( response . data . auth . info3 . pass_last_set_time + response . data . auth . policy . expire > time ( NULL ) ) ) {
int days = response . data . auth . policy . expire / SECONDS_PER_DAY ;
if ( days < = DAYS_TO_WARN_BEFORE_PWD_EXPIRES ) {
_make_remark_format ( pamh , PAM_TEXT_INFO , " Your password will expire in %d days " , days ) ;
}
}
if ( response . data . auth . info3 . user_flgs & LOGON_CACHED_ACCOUNT ) {
_make_remark ( pamh , PAM_ERROR_MSG , " Logging on using cached account. Network ressources can be unavailable " ) ;
2006-05-03 14:19:49 +04:00
_pam_log_debug ( ctrl , LOG_DEBUG , " User %s logged on using cached account \n " , user ) ;
2006-02-04 01:19:41 +03:00
}
/* save the CIFS homedir for pam_cifs / pam_mount */
if ( response . data . auth . info3 . home_dir [ 0 ] ! = ' \0 ' ) {
2006-08-01 19:31:16 +04:00
int ret2 = pam_set_data ( pamh , PAM_WINBIND_HOMEDIR ,
( void * ) strdup ( response . data . auth . info3 . home_dir ) ,
_pam_winbind_cleanup_func ) ;
if ( ret2 ) {
_pam_log_debug ( ctrl , LOG_DEBUG , " Could not set data: %s " ,
pam_strerror ( pamh , ret2 ) ) ;
2006-02-04 01:19:41 +03:00
}
2006-08-01 19:31:16 +04:00
}
/* save the logon script path for other PAM modules */
if ( response . data . auth . info3 . logon_script [ 0 ] ! = ' \0 ' ) {
int ret2 = pam_set_data ( pamh , PAM_WINBIND_LOGONSCRIPT ,
( void * ) strdup ( response . data . auth . info3 . logon_script ) ,
_pam_winbind_cleanup_func ) ;
if ( ret2 ) {
_pam_log_debug ( ctrl , LOG_DEBUG , " Could not set data: %s " ,
pam_strerror ( pamh , ret2 ) ) ;
}
2006-02-04 01:19:41 +03:00
}
2006-09-05 09:28:31 +04:00
/* If winbindd returned a username, return the pointer to it here. */
if ( user_ret & & response . extra_data . data ) {
/* We have to trust it's a null terminated string. */
* user_ret = response . extra_data . data ;
}
2006-02-04 01:19:41 +03:00
return ret ;
2001-05-04 04:43:20 +04:00
}
/* talk to winbindd */
2006-02-04 01:19:41 +03:00
static int winbind_chauthtok_request ( pam_handle_t * pamh ,
int ctrl ,
const char * user ,
const char * oldpass ,
2006-05-02 23:22:39 +04:00
const char * newpass ,
time_t pwd_last_set )
2001-05-04 04:43:20 +04:00
{
struct winbindd_request request ;
struct winbindd_response response ;
2006-02-04 01:19:41 +03:00
int ret ;
2001-05-04 04:43:20 +04:00
ZERO_STRUCT ( request ) ;
2006-02-04 01:19:41 +03:00
ZERO_STRUCT ( response ) ;
2001-05-04 04:43:20 +04:00
2006-02-04 01:19:41 +03:00
if ( request . data . chauthtok . user = = NULL ) return - 2 ;
2001-05-04 04:43:20 +04:00
strncpy ( request . data . chauthtok . user , user ,
2006-02-04 01:19:41 +03:00
sizeof ( request . data . chauthtok . user ) - 1 ) ;
if ( oldpass ! = NULL ) {
strncpy ( request . data . chauthtok . oldpass , oldpass ,
sizeof ( request . data . chauthtok . oldpass ) - 1 ) ;
} else {
request . data . chauthtok . oldpass [ 0 ] = ' \0 ' ;
}
2001-05-04 04:43:20 +04:00
2006-02-04 01:19:41 +03:00
if ( newpass ! = NULL ) {
strncpy ( request . data . chauthtok . newpass , newpass ,
sizeof ( request . data . chauthtok . newpass ) - 1 ) ;
} else {
request . data . chauthtok . newpass [ 0 ] = ' \0 ' ;
}
if ( ctrl & WINBIND_KRB5_AUTH ) {
2006-08-23 02:53:08 +04:00
request . flags = WBFLAG_PAM_KRB5 | WBFLAG_PAM_CONTACT_TRUSTDOM ;
2006-02-04 01:19:41 +03:00
}
ret = pam_winbind_request_log ( pamh , ctrl , WINBINDD_PAM_CHAUTHTOK , & request , & response , user ) ;
if ( ret = = PAM_SUCCESS ) {
return ret ;
}
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_BACKUP_CONTROLLER " ) ;
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_ACCESS_DENIED " ) ;
/* TODO: tell the min pwd length ? */
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_PWD_TOO_SHORT " ) ;
/* TODO: tell the minage ? */
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_PWD_TOO_RECENT " ) ;
/* TODO: tell the history length ? */
PAM_WB_REMARK_CHECK_RESPONSE_RET ( pamh , response , " NT_STATUS_PWD_HISTORY_CONFLICT " ) ;
2006-04-11 18:40:53 +04:00
if ( ! strcasecmp ( response . data . auth . nt_status_string , " NT_STATUS_PASSWORD_RESTRICTION " ) ) {
2006-02-04 01:19:41 +03:00
/* FIXME: avoid to send multiple PAM messages after another */
switch ( response . data . auth . reject_reason ) {
2006-05-02 23:22:39 +04:00
case - 1 :
break ;
case REJECT_REASON_OTHER :
2006-05-03 00:03:30 +04:00
if ( ( response . data . auth . policy . min_passwordage > 0 ) & &
( pwd_last_set + response . data . auth . policy . min_passwordage > time ( NULL ) ) ) {
2006-05-02 23:22:39 +04:00
PAM_WB_REMARK_DIRECT ( pamh , " NT_STATUS_PWD_TOO_RECENT " ) ;
}
2006-02-04 01:19:41 +03:00
break ;
case REJECT_REASON_TOO_SHORT :
PAM_WB_REMARK_DIRECT ( pamh , " NT_STATUS_PWD_TOO_SHORT " ) ;
break ;
case REJECT_REASON_IN_HISTORY :
PAM_WB_REMARK_DIRECT ( pamh , " NT_STATUS_PWD_HISTORY_CONFLICT " ) ;
break ;
case REJECT_REASON_NOT_COMPLEX :
_make_remark ( pamh , PAM_ERROR_MSG , " Password does not meet complexity requirements " ) ;
break ;
default :
_pam_log_debug ( ctrl , LOG_DEBUG ,
" unknown password change reject reason: %d " ,
response . data . auth . reject_reason ) ;
break ;
}
_make_remark_format ( pamh , PAM_ERROR_MSG ,
" Your password must be at least %d characters; "
" cannot repeat any of the your previous %d passwords "
" %s. "
" Please type a different password. "
" Type a password which meets these requirements in both text boxes. " ,
response . data . auth . policy . min_length_password ,
response . data . auth . policy . password_history ,
( response . data . auth . policy . password_properties & DOMAIN_PASSWORD_COMPLEX ) ?
" ; must contain capitals, numerals or punctuation; and cannot contain your account or full name " :
" " ) ;
}
return ret ;
2001-05-04 04:43:20 +04:00
}
2000-05-09 15:43:00 +04:00
/*
* Checks if a user has an account
*
* return values :
* 1 = User not found
* 0 = OK
* - 1 = System error
*/
2006-02-13 18:12:22 +03:00
static int valid_user ( const char * user , pam_handle_t * pamh , int ctrl )
2000-05-09 15:43:00 +04:00
{
2006-02-13 18:12:22 +03:00
/* check not only if the user is available over NSS calls, also make
* sure it ' s really a winbind user , this is important when stacking PAM
* modules in the ' account ' or ' password ' facility . */
struct passwd * pwd = NULL ;
struct winbindd_request request ;
struct winbindd_response response ;
int ret ;
ZERO_STRUCT ( request ) ;
ZERO_STRUCT ( response ) ;
pwd = getpwnam ( user ) ;
if ( pwd = = NULL ) {
return 1 ;
}
2006-04-11 18:40:53 +04:00
strncpy ( request . data . username , user ,
sizeof ( request . data . username ) - 1 ) ;
2006-02-13 18:12:22 +03:00
ret = pam_winbind_request_log ( pamh , ctrl , WINBINDD_GETPWNAM , & request , & response , user ) ;
switch ( ret ) {
case PAM_USER_UNKNOWN :
return 1 ;
case PAM_SUCCESS :
return 0 ;
default :
break ;
}
return - 1 ;
2000-05-09 15:43:00 +04:00
}
static char * _pam_delete ( register char * xx )
{
2006-02-04 01:19:41 +03:00
_pam_overwrite ( xx ) ;
_pam_drop ( xx ) ;
return NULL ;
2000-05-09 15:43:00 +04:00
}
/*
2002-02-05 12:40:36 +03:00
* obtain a password from the user
2000-05-09 15:43:00 +04:00
*/
2002-02-05 12:40:36 +03:00
2006-02-04 01:19:41 +03:00
static int _winbind_read_password ( pam_handle_t * pamh ,
unsigned int ctrl ,
const char * comment ,
const char * prompt1 ,
const char * prompt2 ,
const char * * pass )
2000-05-09 15:43:00 +04:00
{
2002-02-05 12:40:36 +03:00
int authtok_flag ;
int retval ;
const char * item ;
char * token ;
/*
* make sure nothing inappropriate gets returned
*/
* pass = token = NULL ;
/*
* which authentication token are we getting ?
*/
authtok_flag = on ( WINBIND__OLD_PASSWORD , ctrl ) ? PAM_OLDAUTHTOK : PAM_AUTHTOK ;
/*
* should we obtain the password from a PAM item ?
*/
if ( on ( WINBIND_TRY_FIRST_PASS_ARG , ctrl ) | | on ( WINBIND_USE_FIRST_PASS_ARG , ctrl ) ) {
2006-07-11 22:01:26 +04:00
retval = _pam_get_item ( pamh , authtok_flag , & item ) ;
2002-02-05 12:40:36 +03:00
if ( retval ! = PAM_SUCCESS ) {
/* very strange. */
_pam_log ( LOG_ALERT ,
" pam_get_item returned error to unix-read-password "
) ;
return retval ;
} else if ( item ! = NULL ) { /* we have a password! */
* pass = item ;
item = NULL ;
return PAM_SUCCESS ;
} else if ( on ( WINBIND_USE_FIRST_PASS_ARG , ctrl ) ) {
return PAM_AUTHTOK_RECOVER_ERR ; /* didn't work */
} else if ( on ( WINBIND_USE_AUTHTOK_ARG , ctrl )
& & off ( WINBIND__OLD_PASSWORD , ctrl ) ) {
return PAM_AUTHTOK_RECOVER_ERR ;
}
}
/*
* getting here implies we will have to get the password from the
* user directly .
*/
{
struct pam_message msg [ 3 ] , * pmsg [ 3 ] ;
struct pam_response * resp ;
int i , replies ;
/* prepare to converse */
if ( comment ! = NULL ) {
pmsg [ 0 ] = & msg [ 0 ] ;
msg [ 0 ] . msg_style = PAM_TEXT_INFO ;
2006-05-07 00:12:42 +04:00
msg [ 0 ] . msg = CONST_DISCARD ( char * , comment ) ;
2002-02-05 12:40:36 +03:00
i = 1 ;
} else {
i = 0 ;
}
pmsg [ i ] = & msg [ i ] ;
msg [ i ] . msg_style = PAM_PROMPT_ECHO_OFF ;
2006-05-07 00:12:42 +04:00
msg [ i + + ] . msg = CONST_DISCARD ( char * , prompt1 ) ;
2002-02-05 12:40:36 +03:00
replies = 1 ;
if ( prompt2 ! = NULL ) {
pmsg [ i ] = & msg [ i ] ;
msg [ i ] . msg_style = PAM_PROMPT_ECHO_OFF ;
2006-05-07 00:12:42 +04:00
msg [ i + + ] . msg = CONST_DISCARD ( char * , prompt2 ) ;
2002-02-05 12:40:36 +03:00
+ + replies ;
}
/* so call the conversation expecting i responses */
resp = NULL ;
retval = converse ( pamh , i , pmsg , & resp ) ;
if ( resp ! = NULL ) {
/* interpret the response */
if ( retval = = PAM_SUCCESS ) { /* a good conversation */
2006-04-11 18:40:53 +04:00
token = x_strdup ( resp [ i - replies ] . resp ) ;
2002-02-05 12:40:36 +03:00
if ( token ! = NULL ) {
if ( replies = = 2 ) {
/* verify that password entered correctly */
if ( ! resp [ i - 1 ] . resp
2006-04-11 18:40:53 +04:00
| | strcmp ( token , resp [ i - 1 ] . resp ) ) {
2002-02-05 12:40:36 +03:00
_pam_delete ( token ) ; /* mistyped */
retval = PAM_AUTHTOK_RECOVER_ERR ;
2006-02-04 01:19:41 +03:00
_make_remark ( pamh , PAM_ERROR_MSG , MISTYPED_PASS ) ;
2002-02-05 12:40:36 +03:00
}
}
} else {
2006-07-11 14:39:32 +04:00
_pam_log ( LOG_NOTICE , " could not recover authentication token " ) ;
retval = PAM_AUTHTOK_RECOVER_ERR ;
2002-02-05 12:40:36 +03:00
}
}
/*
* tidy up the conversation ( resp_retcode ) is ignored
* - - what is it for anyway ? AGM
*/
_pam_drop_reply ( resp , i ) ;
} else {
retval = ( retval = = PAM_SUCCESS )
? PAM_AUTHTOK_RECOVER_ERR : retval ;
}
2000-05-09 15:43:00 +04:00
}
2002-02-05 12:40:36 +03:00
if ( retval ! = PAM_SUCCESS ) {
2006-02-04 01:19:41 +03:00
_pam_log_debug ( ctrl , LOG_DEBUG ,
2002-02-05 12:40:36 +03:00
" unable to obtain a password " ) ;
return retval ;
2000-05-09 15:43:00 +04:00
}
2002-02-05 12:40:36 +03:00
/* 'token' is the entered password */
/* we store this password as an item */
2000-05-09 15:43:00 +04:00
2002-02-05 12:40:36 +03:00
retval = pam_set_item ( pamh , authtok_flag , token ) ;
_pam_delete ( token ) ; /* clean it up */
2006-02-04 01:19:41 +03:00
if ( retval ! = PAM_SUCCESS | |
2006-07-11 22:01:26 +04:00
( retval = _pam_get_item ( pamh , authtok_flag , & item ) ) ! = PAM_SUCCESS ) {
2002-02-05 12:40:36 +03:00
_pam_log ( LOG_CRIT , " error manipulating password " ) ;
return retval ;
}
2000-05-09 15:43:00 +04:00
2002-02-05 12:40:36 +03:00
* pass = item ;
item = NULL ; /* break link to password */
return PAM_SUCCESS ;
2000-05-09 15:43:00 +04:00
}
2006-02-04 01:19:41 +03:00
const char * get_conf_item_string ( int argc ,
const char * * argv ,
2006-04-11 18:40:53 +04:00
int ctrl ,
dictionary * d ,
2006-02-04 01:19:41 +03:00
const char * item ,
int flag )
{
int i = 0 ;
char * parm = NULL ;
const char * parm_opt = NULL ;
2006-04-11 18:40:53 +04:00
char * key = NULL ;
2006-02-04 01:19:41 +03:00
if ( ! ( ctrl & flag ) ) {
goto out ;
}
2006-05-11 01:12:10 +04:00
/* let the pam opt take precedence over the pam_winbind.conf option */
2006-04-11 18:40:53 +04:00
if ( d ! = NULL ) {
2006-04-11 19:18:46 +04:00
if ( ! asprintf ( & key , " global:%s " , item ) ) {
2006-04-11 18:40:53 +04:00
goto out ;
}
parm_opt = iniparser_getstr ( d , key ) ;
SAFE_FREE ( key ) ;
}
2006-02-04 01:19:41 +03:00
for ( i = 0 ; i < argc ; i + + ) {
if ( ( strncmp ( argv [ i ] , item , strlen ( item ) ) = = 0 ) ) {
char * p ;
2006-04-11 18:40:53 +04:00
parm = strdup ( argv [ i ] ) ;
2006-02-04 01:19:41 +03:00
if ( ( p = strchr ( parm , ' = ' ) ) = = NULL ) {
_pam_log ( LOG_INFO , " no \" = \" delimiter for \" %s \" found \n " , item ) ;
goto out ;
}
SAFE_FREE ( parm ) ;
_pam_log_debug ( ctrl , LOG_INFO , " PAM config: %s '%s' \n " , item , p + 1 ) ;
return p + 1 ;
}
}
2006-05-11 01:12:10 +04:00
if ( d ! = NULL ) {
_pam_log_debug ( ctrl , LOG_INFO , " CONFIG file: %s '%s' \n " , item , parm_opt ) ;
}
2006-02-04 01:19:41 +03:00
out :
SAFE_FREE ( parm ) ;
return parm_opt ;
}
2006-04-11 18:40:53 +04:00
const char * get_krb5_cc_type_from_config ( int argc , const char * * argv , int ctrl , dictionary * d )
2006-02-04 01:19:41 +03:00
{
2006-04-11 18:40:53 +04:00
return get_conf_item_string ( argc , argv , ctrl , d , " krb5_ccache_type " , WINBIND_KRB5_CCACHE_TYPE ) ;
2006-02-04 01:19:41 +03:00
}
2006-04-11 18:40:53 +04:00
const char * get_member_from_config ( int argc , const char * * argv , int ctrl , dictionary * d )
2006-02-04 01:19:41 +03:00
{
const char * ret = NULL ;
2006-04-11 18:40:53 +04:00
ret = get_conf_item_string ( argc , argv , ctrl , d , " require_membership_of " , WINBIND_REQUIRED_MEMBERSHIP ) ;
2006-02-04 01:19:41 +03:00
if ( ret ) {
return ret ;
}
2006-04-11 18:40:53 +04:00
return get_conf_item_string ( argc , argv , ctrl , d , " require-membership-of " , WINBIND_REQUIRED_MEMBERSHIP ) ;
2006-02-04 01:19:41 +03:00
}
2000-05-09 15:43:00 +04:00
PAM_EXTERN
int pam_sm_authenticate ( pam_handle_t * pamh , int flags ,
int argc , const char * * argv )
{
2006-02-04 01:19:41 +03:00
const char * username ;
const char * password ;
const char * member = NULL ;
const char * cctype = NULL ;
int retval = PAM_AUTH_ERR ;
2006-04-11 18:40:53 +04:00
dictionary * d ;
2006-09-05 09:28:31 +04:00
char * username_ret = NULL ;
2006-02-04 01:19:41 +03:00
/* parse arguments */
2006-04-11 18:40:53 +04:00
int ctrl = _pam_parse ( argc , argv , & d ) ;
2006-02-04 01:19:41 +03:00
if ( ctrl = = - 1 ) {
2006-04-11 18:40:53 +04:00
retval = PAM_SYSTEM_ERR ;
goto out ;
2006-02-04 01:19:41 +03:00
}
2006-05-12 23:16:10 +04:00
_pam_log_debug ( ctrl , LOG_DEBUG , " pam_winbind: pam_sm_authenticate (flags: 0x%04x) " , flags ) ;
2006-02-04 01:19:41 +03:00
/* Get the username */
retval = pam_get_user ( pamh , & username , NULL ) ;
if ( ( retval ! = PAM_SUCCESS ) | | ( ! username ) ) {
_pam_log_debug ( ctrl , LOG_DEBUG , " can not get the username " ) ;
2006-04-11 18:40:53 +04:00
retval = PAM_SERVICE_ERR ;
goto out ;
2006-02-04 01:19:41 +03:00
}
retval = _winbind_read_password ( pamh , ctrl , NULL ,
" Password: " , NULL ,
& password ) ;
if ( retval ! = PAM_SUCCESS ) {
_pam_log ( LOG_ERR , " Could not retrieve user's password " ) ;
2006-04-11 18:40:53 +04:00
retval = PAM_AUTHTOK_ERR ;
goto out ;
2006-02-04 01:19:41 +03:00
}
/* Let's not give too much away in the log file */
2002-01-07 05:57:06 +03:00
# ifdef DEBUG_PASSWORD
2006-02-04 01:19:41 +03:00
_pam_log_debug ( ctrl , LOG_INFO , " Verify user `%s' with password `%s' " ,
username , password ) ;
2002-01-07 05:57:06 +03:00
# else
2006-02-04 01:19:41 +03:00
_pam_log_debug ( ctrl , LOG_INFO , " Verify user `%s' " , username ) ;
2002-01-07 05:57:06 +03:00
# endif
2006-04-11 18:40:53 +04:00
member = get_member_from_config ( argc , argv , ctrl , d ) ;
2004-08-18 20:25:41 +04:00
2006-04-11 18:40:53 +04:00
cctype = get_krb5_cc_type_from_config ( argc , argv , ctrl , d ) ;
2004-08-18 20:25:41 +04:00
2006-02-04 01:19:41 +03:00
/* Now use the username to look up password */
2006-09-05 09:28:31 +04:00
retval = winbind_auth_request ( pamh , ctrl , username , password , member ,
cctype , True , NULL , & username_ret ) ;
2004-08-18 20:25:41 +04:00
2006-02-04 01:19:41 +03:00
if ( retval = = PAM_NEW_AUTHTOK_REQD | |
retval = = PAM_AUTHTOK_EXPIRED ) {
2004-08-18 20:25:41 +04:00
2006-02-04 01:19:41 +03:00
char * buf ;
2004-08-18 20:25:41 +04:00
2006-02-04 01:19:41 +03:00
if ( ! asprintf ( & buf , " %d " , retval ) ) {
2006-04-11 18:40:53 +04:00
retval = PAM_BUF_ERR ;
goto out ;
2006-02-04 01:19:41 +03:00
}
2006-01-13 14:11:23 +03:00
2006-02-04 01:19:41 +03:00
pam_set_data ( pamh , PAM_WINBIND_NEW_AUTHTOK_REQD , ( void * ) buf , _pam_winbind_cleanup_func ) ;
2006-01-13 14:11:23 +03:00
2006-04-11 18:40:53 +04:00
retval = PAM_SUCCESS ;
goto out ;
2006-02-04 01:19:41 +03:00
}
2006-04-11 18:40:53 +04:00
out :
2006-09-05 09:28:31 +04:00
if ( username_ret ) {
pam_set_item ( pamh , PAM_USER , username_ret ) ;
free ( username_ret ) ;
}
2006-04-11 18:40:53 +04:00
if ( d ) {
iniparser_freedict ( d ) ;
}
2006-02-04 01:19:41 +03:00
return retval ;
2000-05-09 15:43:00 +04:00
}
PAM_EXTERN
int pam_sm_setcred ( pam_handle_t * pamh , int flags ,
int argc , const char * * argv )
{
2006-02-04 01:19:41 +03:00
/* parse arguments */
2006-04-11 18:40:53 +04:00
int ctrl = _pam_parse ( argc , argv , NULL ) ;
2006-02-04 01:19:41 +03:00
if ( ctrl = = - 1 ) {
return PAM_SYSTEM_ERR ;
}
2006-05-12 23:16:10 +04:00
_pam_log_debug ( ctrl , LOG_DEBUG , " pam_winbind: pam_sm_setcred (flags: 0x%04x) " , flags ) ;
2006-02-04 01:19:41 +03:00
if ( flags & PAM_DELETE_CRED ) {
return pam_sm_close_session ( pamh , flags , argc , argv ) ;
}
return PAM_SUCCESS ;
2000-05-09 15:43:00 +04:00
}
/*
* Account management . We want to verify that the account exists
* before returning PAM_SUCCESS
*/
PAM_EXTERN
int pam_sm_acct_mgmt ( pam_handle_t * pamh , int flags ,
int argc , const char * * argv )
{
2006-02-04 01:19:41 +03:00
const char * username ;
int retval = PAM_USER_UNKNOWN ;
void * tmp = NULL ;
/* parse arguments */
2006-04-11 18:40:53 +04:00
int ctrl = _pam_parse ( argc , argv , NULL ) ;
2006-02-04 01:19:41 +03:00
if ( ctrl = = - 1 ) {
return PAM_SYSTEM_ERR ;
}
2006-01-13 14:11:23 +03:00
2006-05-12 23:16:10 +04:00
_pam_log_debug ( ctrl , LOG_DEBUG , " pam_winbind: pam_sm_acct_mgmt (flags: 0x%04x) " , flags ) ;
2000-05-09 15:43:00 +04:00
2006-02-04 01:19:41 +03:00
/* Get the username */
retval = pam_get_user ( pamh , & username , NULL ) ;
if ( ( retval ! = PAM_SUCCESS ) | | ( ! username ) ) {
_pam_log_debug ( ctrl , LOG_DEBUG , " can not get the username " ) ;
return PAM_SERVICE_ERR ;
}
2000-05-09 15:43:00 +04:00
2006-02-04 01:19:41 +03:00
/* Verify the username */
2006-02-13 18:12:22 +03:00
retval = valid_user ( username , pamh , ctrl ) ;
2006-02-04 01:19:41 +03:00
switch ( retval ) {
2000-05-09 15:43:00 +04:00
case - 1 :
2006-02-04 01:19:41 +03:00
/* some sort of system error. The log was already printed */
return PAM_SERVICE_ERR ;
2000-05-09 15:43:00 +04:00
case 1 :
2006-02-04 01:19:41 +03:00
/* the user does not exist */
_pam_log_debug ( ctrl , LOG_NOTICE , " user `%s' not found " , username ) ;
if ( ctrl & WINBIND_UNKNOWN_OK_ARG ) {
return PAM_IGNORE ;
}
return PAM_USER_UNKNOWN ;
2000-05-09 15:43:00 +04:00
case 0 :
2006-02-04 01:19:41 +03:00
pam_get_data ( pamh , PAM_WINBIND_NEW_AUTHTOK_REQD , ( const void * * ) & tmp ) ;
if ( tmp ! = NULL ) {
2006-08-17 15:54:23 +04:00
retval = atoi ( ( const char * ) tmp ) ;
2006-02-04 01:19:41 +03:00
switch ( retval ) {
case PAM_AUTHTOK_EXPIRED :
/* fall through, since new token is required in this case */
case PAM_NEW_AUTHTOK_REQD :
_pam_log ( LOG_WARNING , " pam_sm_acct_mgmt success but %s is set " ,
PAM_WINBIND_NEW_AUTHTOK_REQD ) ;
_pam_log ( LOG_NOTICE , " user '%s' needs new password " , username ) ;
/* PAM_AUTHTOKEN_REQD does not exist, but is documented in the manpage */
return PAM_NEW_AUTHTOK_REQD ;
default :
_pam_log ( LOG_WARNING , " pam_sm_acct_mgmt success " ) ;
_pam_log ( LOG_NOTICE , " user '%s' granted access " , username ) ;
return PAM_SUCCESS ;
}
}
/* Otherwise, the authentication looked good */
_pam_log ( LOG_NOTICE , " user '%s' granted access " , username ) ;
return PAM_SUCCESS ;
2000-05-09 15:43:00 +04:00
default :
2006-02-04 01:19:41 +03:00
/* we don't know anything about this return value */
_pam_log ( LOG_ERR , " internal module error (retval = %d, user = `%s') " ,
retval , username ) ;
return PAM_SERVICE_ERR ;
}
/* should not be reached */
return PAM_IGNORE ;
2000-05-09 15:43:00 +04:00
}
2006-02-04 01:19:41 +03:00
2002-04-04 01:39:01 +04:00
PAM_EXTERN
int pam_sm_open_session ( pam_handle_t * pamh , int flags ,
2006-02-04 01:19:41 +03:00
int argc , const char * * argv )
2002-04-04 01:39:01 +04:00
{
2006-02-04 01:19:41 +03:00
/* parse arguments */
2006-04-11 18:40:53 +04:00
int ctrl = _pam_parse ( argc , argv , NULL ) ;
2006-02-04 01:19:41 +03:00
if ( ctrl = = - 1 ) {
return PAM_SYSTEM_ERR ;
}
2006-05-12 23:16:10 +04:00
_pam_log_debug ( ctrl , LOG_DEBUG , " pam_winbind: pam_sm_open_session handler (flags: 0x%04x) " , flags ) ;
2006-02-04 01:19:41 +03:00
return PAM_SUCCESS ;
2002-04-04 01:39:01 +04:00
}
2006-02-04 01:19:41 +03:00
2002-04-04 01:39:01 +04:00
PAM_EXTERN
int pam_sm_close_session ( pam_handle_t * pamh , int flags ,
2006-02-04 01:19:41 +03:00
int argc , const char * * argv )
2002-04-04 01:39:01 +04:00
{
2006-04-11 18:40:53 +04:00
dictionary * d ;
2006-04-11 19:11:27 +04:00
int retval = PAM_SUCCESS ;
2006-04-11 18:40:53 +04:00
2006-02-04 01:19:41 +03:00
/* parse arguments */
2006-04-11 18:40:53 +04:00
int ctrl = _pam_parse ( argc , argv , & d ) ;
2006-02-04 01:19:41 +03:00
if ( ctrl = = - 1 ) {
2006-04-11 18:40:53 +04:00
retval = PAM_SYSTEM_ERR ;
goto out ;
2006-02-04 01:19:41 +03:00
}
2006-05-12 23:16:10 +04:00
_pam_log_debug ( ctrl , LOG_DEBUG , " pam_winbind: pam_sm_close_session handler (flags: 0x%04x) " , flags ) ;
2006-02-04 01:19:41 +03:00
if ( ! ( flags & PAM_DELETE_CRED ) ) {
2006-04-11 18:40:53 +04:00
retval = PAM_SUCCESS ;
goto out ;
2006-02-04 01:19:41 +03:00
}
if ( ctrl & WINBIND_KRB5_AUTH ) {
/* destroy the ccache here */
struct winbindd_request request ;
struct winbindd_response response ;
const char * user ;
const char * ccname = NULL ;
struct passwd * pwd = NULL ;
ZERO_STRUCT ( request ) ;
ZERO_STRUCT ( response ) ;
retval = pam_get_user ( pamh , & user , " Username: " ) ;
if ( retval = = PAM_SUCCESS ) {
if ( user = = NULL ) {
_pam_log ( LOG_ERR , " username was NULL! " ) ;
2006-04-11 18:40:53 +04:00
retval = PAM_USER_UNKNOWN ;
goto out ;
2006-02-04 01:19:41 +03:00
}
if ( retval = = PAM_SUCCESS ) {
_pam_log_debug ( ctrl , LOG_DEBUG , " username [%s] obtained " , user ) ;
}
} else {
_pam_log_debug ( ctrl , LOG_DEBUG , " could not identify user " ) ;
2006-04-11 18:40:53 +04:00
goto out ;
2006-02-04 01:19:41 +03:00
}
ccname = pam_getenv ( pamh , " KRB5CCNAME " ) ;
if ( ccname = = NULL ) {
_pam_log_debug ( ctrl , LOG_DEBUG , " user has no KRB5CCNAME environment " ) ;
2006-04-11 18:40:53 +04:00
retval = PAM_SUCCESS ;
goto out ;
2006-02-04 01:19:41 +03:00
}
2006-04-11 18:40:53 +04:00
strncpy ( request . data . logoff . user , user ,
sizeof ( request . data . logoff . user ) - 1 ) ;
strncpy ( request . data . logoff . krb5ccname , ccname ,
sizeof ( request . data . logoff . krb5ccname ) - 1 ) ;
2006-02-04 01:19:41 +03:00
pwd = getpwnam ( user ) ;
if ( pwd = = NULL ) {
2006-04-11 18:40:53 +04:00
retval = PAM_USER_UNKNOWN ;
goto out ;
2006-02-04 01:19:41 +03:00
}
request . data . logoff . uid = pwd - > pw_uid ;
2006-08-23 02:53:08 +04:00
request . flags = WBFLAG_PAM_KRB5 | WBFLAG_PAM_CONTACT_TRUSTDOM ;
2006-02-04 01:19:41 +03:00
2006-04-11 18:40:53 +04:00
retval = pam_winbind_request_log ( pamh , ctrl , WINBINDD_PAM_LOGOFF , & request , & response , user ) ;
2006-02-04 01:19:41 +03:00
}
2006-04-11 18:40:53 +04:00
out :
if ( d ) {
iniparser_freedict ( d ) ;
}
return retval ;
2002-04-04 01:39:01 +04:00
}
2000-05-09 15:43:00 +04:00
2006-02-04 01:19:41 +03:00
PAM_EXTERN
int pam_sm_chauthtok ( pam_handle_t * pamh , int flags ,
int argc , const char * * argv )
2001-05-04 04:43:20 +04:00
{
2002-02-05 12:40:36 +03:00
unsigned int lctrl ;
int retval ;
2006-02-04 01:19:41 +03:00
unsigned int ctrl ;
2001-05-04 04:43:20 +04:00
2002-02-05 12:40:36 +03:00
/* <DO NOT free() THESE> */
const char * user ;
char * pass_old , * pass_new ;
/* </DO NOT free() THESE> */
2001-05-04 04:43:20 +04:00
2006-04-11 18:40:53 +04:00
char * Announce ;
2002-02-05 12:40:36 +03:00
int retry = 0 ;
2006-04-11 18:40:53 +04:00
dictionary * d ;
2002-02-05 12:40:36 +03:00
2006-04-11 18:40:53 +04:00
ctrl = _pam_parse ( argc , argv , & d ) ;
2006-02-04 01:19:41 +03:00
if ( ctrl = = - 1 ) {
2006-04-11 18:40:53 +04:00
retval = PAM_SYSTEM_ERR ;
goto out ;
2006-02-04 01:19:41 +03:00
}
2006-05-12 23:16:10 +04:00
_pam_log_debug ( ctrl , LOG_DEBUG , " pam_winbind: pam_sm_chauthtok (flags: 0x%04x) " , flags ) ;
2006-02-04 01:19:41 +03:00
2006-05-03 19:19:31 +04:00
/* clearing offline bit for the auth in the password change */
ctrl & = ~ WINBIND_CACHED_LOGIN ;
2002-02-05 12:40:36 +03:00
/*
* First get the name of a user
*/
retval = pam_get_user ( pamh , & user , " Username: " ) ;
if ( retval = = PAM_SUCCESS ) {
if ( user = = NULL ) {
_pam_log ( LOG_ERR , " username was NULL! " ) ;
2006-04-11 18:40:53 +04:00
retval = PAM_USER_UNKNOWN ;
goto out ;
2002-02-05 12:40:36 +03:00
}
2006-02-04 01:19:41 +03:00
if ( retval = = PAM_SUCCESS ) {
_pam_log_debug ( ctrl , LOG_DEBUG , " username [%s] obtained " ,
2002-02-05 12:40:36 +03:00
user ) ;
2006-02-04 01:19:41 +03:00
}
2002-02-05 12:40:36 +03:00
} else {
2006-02-04 01:19:41 +03:00
_pam_log_debug ( ctrl , LOG_DEBUG ,
" password - could not identify user " ) ;
2006-04-11 18:40:53 +04:00
goto out ;
2002-02-05 12:40:36 +03:00
}
2001-05-04 04:43:20 +04:00
2006-02-13 18:12:22 +03:00
/* check if this is really a user in winbindd, not only in NSS */
retval = valid_user ( user , pamh , ctrl ) ;
switch ( retval ) {
case 1 :
2006-04-11 18:40:53 +04:00
retval = PAM_USER_UNKNOWN ;
goto out ;
2006-02-13 18:12:22 +03:00
case - 1 :
2006-04-11 18:40:53 +04:00
retval = PAM_SYSTEM_ERR ;
goto out ;
2006-02-13 18:12:22 +03:00
default :
break ;
}
2002-02-05 12:40:36 +03:00
/*
* obtain and verify the current password ( OLDAUTHTOK ) for
* the user .
*/
if ( flags & PAM_PRELIM_CHECK ) {
2006-05-02 23:22:39 +04:00
time_t pwdlastset_prelim = 0 ;
2002-02-05 12:40:36 +03:00
/* instruct user what is happening */
# define greeting "Changing password for "
2006-04-11 18:40:53 +04:00
Announce = ( char * ) malloc ( sizeof ( greeting ) + strlen ( user ) ) ;
if ( Announce = = NULL ) {
_pam_log ( LOG_CRIT , " password - out of memory " ) ;
retval = PAM_BUF_ERR ;
goto out ;
}
( void ) strcpy ( Announce , greeting ) ;
( void ) strcpy ( Announce + sizeof ( greeting ) - 1 , user ) ;
2002-02-05 12:40:36 +03:00
# undef greeting
lctrl = ctrl | WINBIND__OLD_PASSWORD ;
2006-02-04 01:19:41 +03:00
retval = _winbind_read_password ( pamh , lctrl ,
Announce ,
" (current) NT password: " ,
NULL ,
( const char * * ) & pass_old ) ;
2002-02-05 12:40:36 +03:00
if ( retval ! = PAM_SUCCESS ) {
2006-02-04 01:19:41 +03:00
_pam_log ( LOG_NOTICE , " password - (old) token not obtained " ) ;
2006-04-11 18:40:53 +04:00
goto out ;
2002-02-05 12:40:36 +03:00
}
/* verify that this is the password for this user */
2006-09-05 09:28:31 +04:00
retval = winbind_auth_request ( pamh , ctrl , user , pass_old ,
NULL , NULL , False , & pwdlastset_prelim , NULL ) ;
2006-02-04 01:19:41 +03:00
2006-05-02 23:22:39 +04:00
if ( retval ! = PAM_ACCT_EXPIRED & &
retval ! = PAM_AUTHTOK_EXPIRED & &
retval ! = PAM_NEW_AUTHTOK_REQD & &
retval ! = PAM_SUCCESS ) {
2002-02-05 12:40:36 +03:00
pass_old = NULL ;
2006-04-11 18:40:53 +04:00
goto out ;
2002-02-05 12:40:36 +03:00
}
2006-05-02 23:22:39 +04:00
pam_set_data ( pamh , PAM_WINBIND_PWD_LAST_SET , ( void * ) pwdlastset_prelim , _pam_winbind_cleanup_func ) ;
2002-02-05 12:40:36 +03:00
retval = pam_set_item ( pamh , PAM_OLDAUTHTOK , ( const void * ) pass_old ) ;
pass_old = NULL ;
if ( retval ! = PAM_SUCCESS ) {
2006-02-04 01:19:41 +03:00
_pam_log ( LOG_CRIT , " failed to set PAM_OLDAUTHTOK " ) ;
2002-02-05 12:40:36 +03:00
}
} else if ( flags & PAM_UPDATE_AUTHTOK ) {
2006-05-02 23:22:39 +04:00
time_t pwdlastset_update = 0 ;
2002-02-05 12:40:36 +03:00
/*
* obtain the proposed password
*/
/*
* get the old token back .
*/
2006-07-11 22:01:26 +04:00
retval = _pam_get_item ( pamh , PAM_OLDAUTHTOK , & pass_old ) ;
2002-02-05 12:40:36 +03:00
if ( retval ! = PAM_SUCCESS ) {
_pam_log ( LOG_NOTICE , " user not authenticated " ) ;
2006-04-11 18:40:53 +04:00
goto out ;
2002-02-05 12:40:36 +03:00
}
lctrl = ctrl ;
if ( on ( WINBIND_USE_AUTHTOK_ARG , lctrl ) ) {
2005-02-04 03:25:33 +03:00
lctrl | = WINBIND_USE_FIRST_PASS_ARG ;
2002-02-05 12:40:36 +03:00
}
retry = 0 ;
retval = PAM_AUTHTOK_ERR ;
while ( ( retval ! = PAM_SUCCESS ) & & ( retry + + < MAX_PASSWD_TRIES ) ) {
/*
* use_authtok is to force the use of a previously entered
* password - - needed for pluggable password strength checking
*/
2006-02-04 01:19:41 +03:00
retval = _winbind_read_password ( pamh , lctrl ,
NULL ,
" Enter new NT password: " ,
" Retype new NT password: " ,
( const char * * ) & pass_new ) ;
2002-02-05 12:40:36 +03:00
if ( retval ! = PAM_SUCCESS ) {
2006-02-04 01:19:41 +03:00
_pam_log_debug ( ctrl , LOG_ALERT
, " password - new password not obtained " ) ;
2002-02-05 12:40:36 +03:00
pass_old = NULL ; /* tidy up */
2006-04-11 18:40:53 +04:00
goto out ;
2002-02-05 12:40:36 +03:00
}
2002-03-12 02:59:20 +03:00
2002-02-05 12:40:36 +03:00
/*
* At this point we know who the user is and what they
* propose as their new password . Verify that the new
* password is acceptable .
*/
if ( pass_new [ 0 ] = = ' \0 ' ) { /* "\0" password = NULL */
pass_new = NULL ;
}
}
/*
* By reaching here we have approved the passwords and must now
* rebuild the password database file .
*/
2006-07-11 22:01:26 +04:00
_pam_get_data ( pamh , PAM_WINBIND_PWD_LAST_SET ,
& pwdlastset_update ) ;
2002-02-05 12:40:36 +03:00
2006-05-02 23:22:39 +04:00
retval = winbind_chauthtok_request ( pamh , ctrl , user , pass_old , pass_new , pwdlastset_update ) ;
2006-02-04 01:19:41 +03:00
if ( retval ) {
_pam_overwrite ( pass_new ) ;
_pam_overwrite ( pass_old ) ;
pass_old = pass_new = NULL ;
2006-04-11 18:40:53 +04:00
goto out ;
2006-02-04 01:19:41 +03:00
}
/* just in case we need krb5 creds after a password change over msrpc */
if ( ctrl & WINBIND_KRB5_AUTH ) {
2006-04-11 18:40:53 +04:00
const char * member = get_member_from_config ( argc , argv , ctrl , d ) ;
const char * cctype = get_krb5_cc_type_from_config ( argc , argv , ctrl , d ) ;
2006-02-04 01:19:41 +03:00
2006-09-05 09:28:31 +04:00
retval = winbind_auth_request ( pamh , ctrl , user , pass_new ,
member , cctype , False , NULL , NULL ) ;
2006-02-04 01:19:41 +03:00
_pam_overwrite ( pass_new ) ;
_pam_overwrite ( pass_old ) ;
pass_old = pass_new = NULL ;
}
2002-02-05 12:40:36 +03:00
} else {
retval = PAM_SERVICE_ERR ;
}
2006-02-04 01:19:41 +03:00
2006-04-11 18:40:53 +04:00
out :
if ( d ) {
iniparser_freedict ( d ) ;
}
2002-02-05 12:40:36 +03:00
return retval ;
2001-05-04 04:43:20 +04:00
}
2000-05-09 15:43:00 +04:00
# ifdef PAM_STATIC
/* static module data */
2001-05-04 04:43:20 +04:00
struct pam_module _pam_winbind_modstruct = {
2006-02-04 01:19:41 +03:00
MODULE_NAME ,
pam_sm_authenticate ,
pam_sm_setcred ,
pam_sm_acct_mgmt ,
pam_sm_open_session ,
pam_sm_close_session ,
pam_sm_chauthtok
2000-05-09 15:43:00 +04:00
} ;
# endif
/*
2002-02-05 12:40:36 +03:00
* Copyright ( c ) Andrew Tridgell < tridge @ samba . org > 2000
* Copyright ( c ) Tim Potter < tpot @ samba . org > 2000
* Copyright ( c ) Andrew Bartlettt < abartlet @ samba . org > 2002
2006-02-04 01:19:41 +03:00
* Copyright ( c ) Guenther Deschner < gd @ samba . org > 2005 - 2006
2006-08-23 02:53:08 +04:00
* Copyright ( c ) Jan Rêkorajski 1999.
2002-02-05 12:40:36 +03:00
* Copyright ( c ) Andrew G . Morgan 1996 - 8.
* Copyright ( c ) Alex O . Yuriev , 1996.
* Copyright ( c ) Cristian Gafton 1996.
* Copyright ( C ) Elliot Lee < sopwith @ redhat . com > 1996 , Red Hat Software .
2000-05-09 15:43:00 +04:00
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions
* are met :
* 1. Redistributions of source code must retain the above copyright
* notice , and the entire permission notice in its entirety ,
* including the disclaimer of warranties .
* 2. Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission .
*
* ALTERNATIVELY , this product may be distributed under the terms of
* the GNU Public License , in which case the provisions of the GPL are
* required INSTEAD OF the above restrictions . ( This clause is
* necessary due to a potential bad interaction between the GPL and
* the restrictions contained in a BSD - style copyright . )
*
* THIS SOFTWARE IS PROVIDED ` AS IS ' ' AND ANY EXPRESS OR IMPLIED
* WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT ,
* INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
* ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION )
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT ,
* STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE )
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE .
*/