Chinaunix首页 | 论坛 | 博客
  • 博客访问: 230360
  • 博文数量: 48
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 412
  • 用 户 组: 普通用户
  • 注册时间: 2013-04-24 10:27
个人简介

Continuous pursuit technical details

文章分类

全部博文(48)

文章存档

2014年(1)

2013年(47)

分类: LINUX

2013-12-04 09:46:19

/*
** FILE:
** SECpamLogModule.c
**
** DESCRIPTION:
** A PAModule (PAM) to authenticate, log, and control platform access.
**
** Access can be via telnet, rlogin, ftp, etc.
** Refer to for more info.
**
** OWNER:
** A. R. Liedtke
** Paul J. Stankus Feature 72150 Add support for IMS defaults
**
** NOTES:
** Ultimately becomes part of SECpamLogModule.so and IMSpamLogModule.so
**
** If file '/usr/lib/security/log_to_mate'
** is present then a copy of the message is sent to the mate.
** This should be done only on CCs
*/


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "luc_compat.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef IMS
#include "cc/hdr/security/IMSdefaults_lx.h"
#else
#include "cc/hdr/security/SECdefaults_lx.h"
#endif


#define SHADOW_DIR "/etc/PSPshadow/"
#define LOCK_EXT ".lock."
#define LOCK_FILE_NAME_LEN sizeof(SHADOW_DIR) + _POSIX_LOGIN_NAME_MAX + sizeof(LOCK_EXT) + 32


#define BLOCKED_ACCT_MSG_LEN 75
char BLOCKED_ACCT_MSG[ BLOCKED_ACCT_MSG_LEN + 3 ]; /* '"'+'"'+'\0' */


int write2mate(char* message);
char *get_user_data(pam_handle_t *pamh, char* buf, size_t length, int extra);
extern int _pam_sm_chauthtok(pam_handle_t * _pamh, const int flags, int argc, const char ** argv);
extern int pam_display(const char * message, char * reply, int rsize);
extern pam_handle_t * glob_pamh;
int is_login_blocked(const char * user, int state);
int is_user_exempt(const char * user);
void prune_login_failures(const char * user);
int from_same_subnet();
int count_bits(int val);
int inetx_atoi(int af, int *lp, char *adr, int swap);
int same_subnet(int *loc, int *rem, int bits);
const char *MapUser(const char *);


int debug = 0;


/*
* Test flags
*/
#define IGNORE_SUBNET_CHECK 0x0001


int test_flag = 0;


int max_simultaneous_logins = DEF_MAX_SIMULTANEOUS_LOGINS;
int max_login_failures =      DEF_MAX_LOGIN_FAILURES; /* greater than 1 */
int lock_timeout =            (DEF_LOCKTIMEOUT * 60); /* in seconds */
int fail_delay_timeout =      (DEF_FAIL_DELAY * 1000000); /* in microseconds */
#ifdef IMS
char * blocked_acct_msgp = "AUTHENTICATION FAILURE"; /* default: IMS msg */
#else
char * blocked_acct_msgp =    NULL; /* default: no msg */
#endif


/*
** NAME:
** load_config()
**
** DESCRIPTION:
** reads file PASSWD_CONFIG and initializes global variables
**
** The values of these variables are not changed if they
** are not defined in file PASSWD_CONFIG
**
** INPUTS:
**  none
**
** RETURNS:
**  0: if failed
**  1: otherwise
**
** CALLS:
**
** SIDE EFFECTS:
**
*/


int load_config(const char * user_name)
{
/* use static variables because we cannot be sure about stack size */
FILE * fp;
static char line[1024];
static char buf[1024];
static char login_failures[1024];
static char simul_logins[1024];
int user_login_failures;
int user_simul_logins;
int user_login_failures_flag;
int user_simul_logins_flag;
int intVal;
char * cptr;
char * iptr;
char * optr;
static int done = 0;


if (done)
return 0;


/* read values from the site config file
*/
fp = fopen(PASSWD_CONFIG, "r");
if (!fp)
return 0;


/* user_name shouldnt be NULL, if this happens set it to "BAD:USER" */
if (!user_name)
user_name = "BAD:USER";


user_login_failures_flag = 0;
user_simul_logins_flag   = 0;


/* create user name specific token */
snprintf(login_failures, sizeof(login_failures),
"MAX_LOGIN_FAILURES_%s=", user_name);


/* create user name specific token */
snprintf(simul_logins, sizeof(simul_logins),
"MAX_SIMULTANEOUS_LOGINS_%s=", user_name);


while (fgets(line, sizeof(line) -1, fp)) {


/* This code loop will only search for parameters as
* the first non-white space token on each line.
*/


/* Use 'buf' for token detection. Use 'line'
* for parameters with embedded white space:
*/
strcpy( buf, line );


/* remove leading white space:
*/
/* "spacetab" */
if( (cptr = strtok( buf, " " )) == NULL )
{
continue;
}


/* trim off all '#' tokens/parameters:
*/
if( *cptr == '#' )
{
continue;
}


if( strstr( cptr, "LOCKTIMEOUT=" ) == cptr )
{
intVal = atoi( cptr+sizeof("LOCKTIMEOUT=")-1 );
lock_timeout = (intVal==DIS_LOCKTIMEOUT)? intVal:
(intVal>MAX_LOCKTIMEOUT)? MAX_LOCKTIMEOUT:
(intVal intVal;
lock_timeout*=60; /* minutes to seconds */
}
else if( strstr( cptr, "FAIL_DELAY=" ) == cptr )
{
intVal = atoi( cptr+sizeof("FAIL_DELAY=")-1 );
fail_delay_timeout = (intVal==DIS_FAIL_DELAY)? intVal:
(intVal>MAX_FAIL_DELAY)? MAX_FAIL_DELAY:
(intVal intVal;
fail_delay_timeout*=1000000; /* seconds to microseconds */
}
else if( strstr( cptr, "MAX_LOGIN_FAILURES=" ) == cptr )
{
intVal = atoi( cptr+sizeof("MAX_LOGIN_FAILURES=")-1 );
max_login_failures = (intVal==DIS_MAX_LOGIN_FAILURES)? intVal:
(intVal>MAX_MAX_LOGIN_FAILURES)? MAX_MAX_LOGIN_FAILURES:
(intVal intVal;
}
else if( strstr( cptr, "MAX_SIMULTANEOUS_LOGINS=" ) == cptr )
{
intVal = atoi( cptr+sizeof("MAX_SIMULTANEOUS_LOGINS=")-1 );
max_simultaneous_logins = (intVal==DIS_MAX_SIMULTANEOUS_LOGINS)? intVal:
(intVal>MAX_MAX_SIMULTANEOUS_LOGINS)? MAX_MAX_SIMULTANEOUS_LOGINS:
(intVal intVal;
}
else if( strstr( cptr, "BLOCKED_ACCT_MSG=" ) == cptr )
{
/* parameter value can have embedded white space:
* parse using 'line' instead of 'buf'
*/
cptr = strstr( line, "BLOCKED_ACCT_MSG=" );
cptr += sizeof("BLOCKED_ACCT_MSG=")-1;
if (*(cptr++) == '"')
{
strncpy( BLOCKED_ACCT_MSG, cptr, BLOCKED_ACCT_MSG_LEN+1 );
BLOCKED_ACCT_MSG[ BLOCKED_ACCT_MSG_LEN ] = '\0';
intVal = strcspn(BLOCKED_ACCT_MSG, "\"");
BLOCKED_ACCT_MSG[ intVal ] = '\0';


optr = iptr = BLOCKED_ACCT_MSG;
while
(
/* while( !EOS and !isprint )
*      outbuf = inbuf
*/
!(intVal = (int)(*(iptr++)))? 0 :
!(isprint( intVal ))? 1 :
(int) (*(optr++) = (char)intVal)
);
*optr = intVal;


blocked_acct_msgp = BLOCKED_ACCT_MSG;
}
}
else if ( strstr( cptr, login_failures ) == cptr )
{
user_login_failures_flag = 1;
intVal = atoi( cptr+strlen(login_failures) );
user_login_failures = (intVal==DIS_MAX_LOGIN_FAILURES)? intVal:
(intVal>MAX_MAX_LOGIN_FAILURES)? MAX_MAX_LOGIN_FAILURES:
(intVal intVal;
}
else if ( strstr( cptr, simul_logins ) == cptr )
{
user_simul_logins_flag = 1;
intVal = atoi( cptr+strlen(simul_logins) );
user_simul_logins = (intVal==DIS_MAX_SIMULTANEOUS_LOGINS)? intVal:
(intVal>MAX_MAX_SIMULTANEOUS_LOGINS)? MAX_MAX_SIMULTANEOUS_LOGINS:
(intVal intVal;
}
}


/* execute these two statements last because they depend on the two
* global "max_......" values being parsed before we possibly
* overwrite them with a user_specific value.
*/
if (user_login_failures_flag != 0)
max_login_failures = user_login_failures;
if (user_simul_logins_flag != 0)
max_simultaneous_logins = user_simul_logins;


fclose(fp);
done = 1;
return 1;
}




/*
** NAME:
** clear_login_failures()
**
** DESCRIPTION:
** clears login failures, so that login failures dont get escalated
**
**
** INPUTS:
** user: login name for which the login failures need to be cleared
** report_only: 
** 1 - report only the number of failed login attempts.
**    Also, clear the count file, but do not clear the
**    lock file(s). 
** 0 - clear the locks and report
**
** RETURNS:
**
** CALLS:
**
** SIDE EFFECTS:
** deletes files /etc/PSPshadow/.lock.*
*/


void clear_login_failures(const char * user, int report_only)
{
char buf[LOCK_FILE_NAME_LEN];
int i;
char msg[512];
struct stat sbuf;
FILE *fp_ec;
char line[20];
int failed = 0;


/* retrieve the number of accumulated log-in failures
*/
snprintf( buf, sizeof(buf), "%s%s%s%i", SHADOW_DIR, MapUser(user),
 LOCK_EXT, 0 );
if( stat( buf, &sbuf ) == 0 )
{
fp_ec = fopen( buf, "r" );
if( fp_ec != NULL )
{
fgets( line, 15, fp_ec );
failed = atoi( line );
fclose( fp_ec );
}
}


if (report_only == 0) {
/* remove all lock files */
int rc = 0;
for (i=1; rc == 0; i++) {
snprintf(buf, sizeof(buf), "%s%s%s%i", SHADOW_DIR, MapUser(user), LOCK_EXT, i);
if ((rc = stat(buf, &sbuf)) == 0)
rc = unlink(buf);
}
}


/* remove the "count" file */
snprintf(buf, sizeof(buf), "%s%s%s%i", SHADOW_DIR, MapUser(user), LOCK_EXT, 0);
unlink(buf);


if (failed != 0)
{
sprintf( msg, "Warning: %d failed login attempt%s since last successful login.", failed, (failed>1)?"s":"" );
pam_display( msg, NULL, 0 );
}
}


/*
** NAME:
** notifyCsop()
**
** DESCRIPTION:
** Notifies SecCsopMsg process when login has been blocked from
** logging in (or block is removed).
**
** The actual blocking/unblocking is handled by other functions.
**
** SecCsopMsg handles the issuing of CSOP message alarms, alarm clearing,
** and the removal of the management files (alarm.user, clear.user).
** This interface method is designed so that if SecCsopMsg doesn't exist,
** or doesn't execute, then this PAM module will not be impacted.
**
** INPUTS:
** user: login name
** state: PAM_AUTH_ERR, PAM_SUCCESS
**
** RETURNS:
**
** CALLS:
**
** SIDE EFFECTS:
**
*/


void notifyCsop(const char * user, int state)
{
FILE *fp_pid = NULL;
int pid;
char line[256];
char buf[512];
int fd;


/* The "pid" file contains the process id of the 'SecCsopMsg' process.
* 'SecCsopMsg' is a daemon process, spawned by cron. When created, it
* writes its PID into the "pid" file so that this function can send
* a signal to it. If the 'SecCsopMsg' daemon terminates with a
* trappable signal, it will remove the "pid" file. This code is
* part of MAS Feature 68450.
*/
fp_pid = fopen( "/var/run/SecCsopMsg.pid", "r" );
if ( (fp_pid == NULL) || (feof(fp_pid) ) )
{
return;
}
while ( !feof(fp_pid) )
{
fgets( line, 255, fp_pid );
pid = atoi( line );
if (pid < 1)
{
fclose( fp_pid );
return;
}
}
fclose( fp_pid );


if ( state == PAM_AUTH_ERR )
{
/* Create/touch the alarm.user file:
*   mtime of alarm.user indicates blocking start time
*/
snprintf(buf, sizeof(buf), "%salarm.%s", SHADOW_DIR, user);
unlink(buf);
fd = open(buf, O_WRONLY|O_CREAT, S_IRWXU);
if (fd >= 0 )
close(fd);
}


/* send the USR1 signal */
kill( pid, SIGUSR1 );
}


/*
** NAME:
** permitted2login()
**
** DESCRIPTION:
** check if login has been locked out
**
** INPUTS:
** user: login name
**
** RETURNS:
** 0 if user is not permitted, 1 otherwise
**
** CALLS:
** notifyCsop(..unblock..)
**
** SIDE EFFECTS:
**
*/


int permitted2login(pam_handle_t *pamh, const char * user)
{
char buf[512];
struct stat sbuf;
struct utmpx * ut;
int fd;
int i;
int login_count = 0;
char * service;


if (pam_get_item(pamh, PAM_SERVICE, (const void **)&service) != PAM_SUCCESS)
{
service = "";
}


if (is_user_exempt(user))
return 1;


setutxent();
while (ut = getutxent())
{
/*
* only count UTMP entries that are not DEAD, and
* the specified PID is alive and kicking.
*/
if (ut->ut_type != DEAD_PROCESS && !strcmp(user, ut->ut_user) && ut->ut_pid > 0 && kill(ut->ut_pid, 0) == 0)
login_count++;
}
endutxent();


/* if the service is not "passwd", check for reasons to reject 
*/
if (strcmp("passwd", service))
{
if ((max_simultaneous_logins != DIS_MAX_SIMULTANEOUS_LOGINS) &&
   (login_count >= max_simultaneous_logins))
{
/*
* Ignore login_count (session audit) if the request is
* internal. Since this is a special case, just return
* success and don't bother pruning old login failures.
* Add something to the log so we can tell when this
* happens.
*/
if (from_same_subnet()) {
sprintf(buf, "PSP_Auth: Ignoring session audit for %s", user);
syslog(LOG_AUTHPRIV | LOG_INFO, buf);
return 1;
}


pam_display("*** Too many simultaneous logins ***\n", NULL, 0);
if (debug)
{
setutxent();
while (ut = getutxent())
{
if (ut->ut_type != DEAD_PROCESS && !strcmp(user, ut->ut_user))
{
snprintf(buf, sizeof(buf), "type: %d; pid: %d; host: %s; line: %s; date: %s"
, ut->ut_type, ut->ut_pid, ut->ut_host, ut->ut_line , ctime((time_t *)&ut->ut_tv.tv_sec));
pam_display(buf, NULL, 0);
}
}
endutxent();
}
return 0;
}


if (is_login_blocked (user, PAM_SUCCESS))
{
int ignore;
const char * env;


/*
* Ignore login_failures if the request is internal
* Since this is a special case, just return
* success and don't bother pruning old login failures.
* Add something to the log so we can tell when this
* happens.
*
* 2011Aug01 - only support the above behavior if an internal OS login
*/
ignore = 0;
if (from_same_subnet()) {
env = pam_getenv(glob_pamh, "PAM_CID");
if (env == NULL || *env == 0)
env="OS";


if (strcmp(env, "OS") == 0)
ignore = 1;
}


if (ignore == 1) {
sprintf(buf, "PSP_Auth: Ignoring login failure block for %s", user);
syslog(LOG_AUTHPRIV | LOG_INFO, buf);
return 1;
}


if (blocked_acct_msgp != NULL)
{
pam_display( blocked_acct_msgp, NULL, 0);
}
return 0;
}
}


/* prune old login failure files every login
*/
prune_login_failures( user );


return 1;
}


/*
** NAME:
** prune_login_failures()
**
** DESCRIPTION:
** removes login failure files which are considered old
**
** removes file /etc/PSPshadow/.lock.
** where is a number between 1 and (max_login_failures-1)
** when the mod time of the file is greater than X minutes old
**
** INPUTS:
** user: login name
**
** RETURNS:
**
** CALLS:
**
** SIDE EFFECTS:
** none
**
*/


void prune_login_failures(const char * user)
{
struct stat sbuf;
int i;


char buf[LOCK_FILE_NAME_LEN];


if( is_user_exempt(user))
return;


for (i=1; i <= max_login_failures-1; i++)
{
snprintf(buf, sizeof(buf), "%s%s%s%i", SHADOW_DIR, MapUser(user),
LOCK_EXT, i);


if (stat(buf, &sbuf) == 0)
{
/* 3600 = 1 hour */
if( (sbuf.st_mtime + 3600 < time(NULL) ) &&
   (sbuf.st_mtime <= time(NULL)) )
{
unlink(buf);
}
}
}
}




/*
** NAME:
** record_login_failure()
**
** DESCRIPTION:
** records a login failure
**
** creates a file /etc/PSPshadow/.lock.
** where is a number between 1 and max_login_failures
** eg. creates file /etc/PSPshadow/.lock.2 for
** second login failure
**
** INPUTS:
** user: login name
**
** RETURNS:
**
** CALLS:
**
** SIDE EFFECTS:
** updates the timestamp on the max_login_failure file
** if that file already exists
**
*/


void record_login_failure(const char * user)
{
struct stat sbuf;
int fd;
int i;
FILE *fp_ec;
char line[20];
int error_cnt;
char *mode;


char buf[LOCK_FILE_NAME_LEN];


if( is_user_exempt(user) )
return;


if (max_login_failures == DIS_MAX_LOGIN_FAILURES)
return;


for (i=1; i <= max_login_failures; i++)
{
snprintf(buf, sizeof(buf), "%s%s%s%i", SHADOW_DIR, MapUser(user),
LOCK_EXT, i);


if (stat(buf, &sbuf) != 0)
break;
}


unlink(buf); /* unnecessary except for last file */
fd = open(buf, O_WRONLY|O_CREAT, S_IRWXU);
if (fd >= 0 )
close(fd);


/* count the accumulated, sequential log-in failures
* store in "user.0"
*/
snprintf(buf, sizeof(buf), "%s%s%s%i", SHADOW_DIR, MapUser(user),
LOCK_EXT, 0);


if (stat(buf, &sbuf) != 0)
mode = "w";
else
mode = "r+";


fp_ec = fopen( buf, mode );
if( fp_ec == NULL )
return;


fgets( line, 15, fp_ec );
if( feof( fp_ec ) )
{
error_cnt = 0;
}
else
{
error_cnt = atoi( line );
rewind( fp_ec );
}
++error_cnt;


fprintf( fp_ec, "%i\n", error_cnt );
fclose( fp_ec );
}




/*
** NAME:
** is_login_blocked()
**
** DESCRIPTION:
** checks if a login is being blocked or entering blocking
**
** checks file /etc/PSPshadow/.lock.
** where is the number "max_login_failures"
**
** INPUTS:
** user: login name
** state: PAM_AUTH_ERR, PAM_SUCCESS
**
** RETURNS:
** 0 login is not being blocked
** 1 login is blocked
**
** CALLS:
** notifyCsop(..block..)
**
** SIDE EFFECTS:
**
*/


int is_login_blocked(const char * user, int state)
{
struct stat sbuf;


char buf[LOCK_FILE_NAME_LEN];


if (is_user_exempt(user))
return 0;


if (max_login_failures == DIS_MAX_LOGIN_FAILURES)
return 0;


snprintf(buf, sizeof(buf), "%s%s%s%i", SHADOW_DIR, MapUser(user),
LOCK_EXT, max_login_failures);


if (stat(buf, &sbuf) != 0) {
/*
* Not yet blocked
*/
return 0;
}


if( lock_timeout == DIS_LOCKTIMEOUT || 
   ((sbuf.st_mtime + lock_timeout > time(NULL)) && (sbuf.st_mtime <= time(NULL))) )
{
/* blocking */
if( state == PAM_AUTH_ERR )
{
/* login failure:
* enter/restart blocking interval
*/
notifyCsop( user, PAM_AUTH_ERR );
}


return 1;
}
else
{
/* leaving blocking interval:
*/
notifyCsop( user, PAM_SUCCESS );


/*
* Don't remove the lock here - let the caller make that decision
*
* unlink(buf);
*/
return 0;
}
}




/*
** NAME:
** is_user_exempt()
**
** DESCRIPTION:
** returns 1 if a user is exempt from certain tests
**
** INPUTS:
** user: login name
**
** RETURNS:
** 0 user is not exempt
** 1 user is exempt (given a pass)
**
** CALLS:
** none
**
** SIDE EFFECTS:
**
*/


int is_user_exempt(const char * user)
{
#ifdef IMS
if (!strcmp(user, "root"))
#else
if (!strcmp(user, "root") || !strcmp(user, "secadmin"))
#endif
return 1;


/*
* Not exempt
*/
return 0;
}




/*
** NAME:
** pam_sm_authenticate()
**
** DESCRIPTION:
** Called by PAM to verify the identity of the current user. The user is
** usually required to enter a password or similar authentication token
** depending upon the authentication scheme configured within the system.
**
** For our platform, the SECpamLogModule.so is placed last in the various
** PAM stacks. This means the authentication token has already been
** obtained (via prompt or key or whatever) by an earlier module in the
** stack and placed in the PAM environment.
**
** This function will perform the actual authentication first and then
** determine if the user is permitted to actually log in.
**
** Since this is the last module (current implementation), this module
** has the last word on whether or not the login should proceed. The
** module "return value" determines the final status.
**
** INPUTS:
** PAM_DISALLOW_NULL_AUTHTOK The authentication service
** should return PAM_AUTH_ERR
** if the user has a null
** authentication token.
**
** RETURNS:
** PAM_SUCCESS authentication was successful
** various authentication was not successful
**
** CALLS:
** get_user_data
**
** CALLED BY:
** pam
**
** SIDE EFFECTS:
** calls syslog() to save user info
*/


int pam_sm_authenticate(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
char *user_name;
char *passwd;
char *status;
char *service;
struct spwd *shadow;
char msg[1024];
char buf[1024];
int pwlen;
int retval = PAM_AUTH_ERR;
static int ssh_password = 0;
int i;
char *encpw;
int blocked;
int report_only;


glob_pamh = pamh;
status = "AUTHENTICATION_FAILED";


/* Parse arguments:
*/
for (i = 0; i < argc; i++)
{
if (strcasecmp(argv[i], "debug") == 0)
{
debug = 1;
continue;
}


if (strcasecmp(argv[i], "test") == 0) {
test_flag = IGNORE_SUBNET_CHECK;
continue;
}
}




if (pam_get_item(pamh, PAM_USER, (const void **)&user_name) != PAM_SUCCESS)
{
user_name = NULL;
}


load_config(user_name);


/* if user_name is NULL, can't do anything else, so bail out
*/
if (user_name == NULL )
{
snprintf(msg,
sizeof(msg),
"PSP_Auth: status: %s; request for authentication; %s",
status,
get_user_data(pamh, buf, sizeof(buf), 1));


syslog(LOG_AUTHPRIV | LOG_INFO, "%s", msg);
write2mate(msg);


return PAM_AUTHINFO_UNAVAIL;
}


/*
* Tell PAM to delay this amount if this method returns an error
*/
pam_fail_delay(pamh, fail_delay_timeout);


/* user name is something, but not necessarily valid
*/
if( (pam_get_item(pamh, PAM_AUTHTOK, (const void **) &passwd) == PAM_SUCCESS) &&
   passwd )
{
/* A password has been obtained from the PAM environment. It
* is assumed that a prior module prompted for it as a result
* of a telnet/ssh/etc command, although it doesn't matter how.
*
* Authenticate the user_name/password combo:
*/


pwlen = strlen( passwd );
shadow = getspnam(user_name);
encpw  = NULL;
if (shadow && shadow->sp_pwdp)
encpw = shadow->sp_pwdp;


/*
* Allow access to dormant accounts if the request originated on the same subnet.
* A dormant account has the password locked (ie: "!!").
*
* The pam_unix module already failed authentication but PAM is configured to ignore
* its PAM_AUTH_ERR. We'll take a second crack at it after skipping the locked tag.
*/
if (encpw && strncmp(encpw, "!!", 2) == 0 && from_same_subnet())
encpw += 2;


if( encpw &&
   !strcmp(encpw, crypt(passwd, encpw)) &&
   pwlen > 0 &&
   pwlen <= MAX_PASSLENGTH )
{
/* user and password authenticated:
* check if blocked
*/


/*
* Moved permit2login to pam_sm_acct_mgmt for two reasons:
*    1) If permit2login fails, it should cause the session to terminate (and
* not ask for the password again).
*    2) Show pam_display messages on the client side. Linux SSH discards these
* messages if PAM_AUTH_ERR is returned (probably to avoid giving away
* information to a hacker).
*
* Still check if the account is blocked.
*/
blocked = is_login_blocked(user_name, PAM_SUCCESS);


if (blocked) {
status = "ACCOUNT_BLOCKED";
/*
* In order to notify the client about the blocked
* condition, the code flow needs to get to the
* pam_sm_acct_mgmt function. Return PAM_SUCCESS
* now and deny the client in pam_sm_acct_mgmt(). 
* retval = PAM_AUTH_ERR;
*/
retval = PAM_SUCCESS;
} else {
status = "AUTHENTICATION_SUCCESS";


if (blocked && from_same_subnet()) {
sprintf(msg, "PSP_Auth: Allowing blocked %s on same subnet", user_name);
syslog(LOG_AUTHPRIV | LOG_INFO, msg);
}


// Only clear blocked status if the account is good, ie:
// - password is unlocked
// - password is not blocked
report_only = 0;
if (strncmp(encpw, "!!", 2) == 0 || blocked)
report_only = 1;


clear_login_failures(user_name, report_only);
retval = PAM_SUCCESS;
}
}
else
{
/* failed authentication:
* due to invalid user name or bad password?
*/
if (getspnam(user_name))
{
/* bad password */
prune_login_failures(user_name);
record_login_failure(user_name);
(void) is_login_blocked(user_name,PAM_AUTH_ERR);
retval = PAM_AUTH_ERR;
}
else
{
/* invalid user name */
retval = PAM_USER_UNKNOWN;
}
}
/* have a PW, from trying to log in
*/
}
else
{
/* no PW:
* user/root may be asking for PW change (passwd cmd)
* authentication already proven by prior login
*/


if( flags & PAM_DISALLOW_NULL_AUTHTOK )
{
retval = PAM_AUTH_ERR;
}
else
{
status = "AUTHENTICATION_SUCCESS";
retval = PAM_SUCCESS;
}
}


snprintf(msg,
sizeof(msg),
"PSP_Auth: status: %s; request for authentication; %s",
status,
get_user_data(pamh, buf, sizeof(buf), 1));


syslog(LOG_AUTHPRIV | LOG_INFO, "%s", msg);
write2mate(msg);


return retval;
}


/*
** NAME:
** pam_sm_open_session()
**
** DESCRIPTION:
** called by pam, after the user has succesfully cleared
** regular unix authentication but before he is let the
** system.
**
** INPUTS:
**
** RETURNS:
** always returns PAM_SUCCESS, it is up to other modules
** to reject the user
**
** CALLS:
** get_user_data
**
** CALLED BY:
** pam
**
** SIDE EFFECTS:
** calls syslog() to save user info
*/


int pam_sm_open_session(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
char msg[1024];
char buf[1024];


glob_pamh = pamh;


snprintf(msg, sizeof(msg), "PSP_Auth: opening connection; %s",
get_user_data(pamh, buf, sizeof(buf), 1));


syslog(LOG_AUTHPRIV | LOG_INFO, "%s", msg);
write2mate(msg);


return PAM_SUCCESS;
}


/*
** NAME:
** pam_sm_close_session()
**
** DESCRIPTION:
** called by pam, when the user is closing the session
** syslogs the closing time.
**
** INPUTS:
**
** RETURNS:
** always returns PAM_SUCCESS, it is up to other modules
** to reject the user
**
** CALLS:
** get_user_data
**
** CALLED BY:
** pam
**
** SIDE EFFECTS:
** calls syslog() to save user info
*/


int pam_sm_close_session(pam_handle_t *pamh, int flags,
int argc, const char **argv)
{
char msg[1024];
char buf[1024];


glob_pamh = pamh;


snprintf(msg, sizeof(msg), "PSP_Auth: closing connection; %s",
get_user_data(pamh, buf, sizeof(buf), 0));


syslog(LOG_AUTHPRIV | LOG_INFO, "%s", msg);


write2mate(msg);


return PAM_SUCCESS;
}


/*
** NAME:
** write2mate(char* message)
**
** DESCRIPTION:
** This function sends a message to the mate CC. It checks for the
** existance of file "/usr/lib/security/log_to_mate" to distinguish
** between a CC and TS (the file should be present only on CC). I
** got the syslogd message format by looking at solaris 2.6 source.
** It is better to use numeric ip address instead of using machine
** names, as dns is often misconfigured on our systems.
**
** INPUTS:
** message: the text to be sent to mate
**
** RETURNS:
** 0: if no err
** non 0: if error
**
** CALLS:
**
** CALLED BY:
** pam_sm_authenticate()
** pam_sm_open_session()
** pam_sm_close_session()
**
** SIDE EFFECTS:
*/


int write2mate(char* message)
{
struct stat temp;
char hname[257];
char mate_name[32];
int sock;
struct sockaddr_in name;
struct hostent *hp = NULL;
ulong_t mate_addr;
char buf[1024];
int status;
int length;


/* send data only if file "/usr/lib/security/log_to_mate" is present */
if (stat("/usr/lib/security/log_to_mate", &temp))
return 0;


// IBM: if (sysinfo(SI_HOSTNAME, hname, sizeof(hname)) == -1)
if (gethostname(hname, sizeof(hname)) == -1)
return 1;


if ((length = strlen(hname)) < 3)
return 1;


switch (hname[length-1]) {


case '0': strcpy(mate_name, "192.12.150.85");
 break;


case '1': strcpy(mate_name, "192.12.150.84");
 break;


default : return 1;
}


if ((int)(mate_addr = inet_addr(mate_name)) == -1) {
return 1;
}


if((hp = gethostbyaddr((char *)&mate_addr, sizeof (mate_addr), AF_INET)) == NULL) {
return 1;
}


bzero (&name, sizeof (name));
memcpy((char *) &name.sin_addr, (char *) hp->h_addr, hp->h_length);
name.sin_family = AF_INET;
name.sin_port = htons(514); /* syslogd listens on this port number */


if ((sock = socket(AF_INET,SOCK_DGRAM, 0)) == -1) {
return 1;
}


snprintf(buf, sizeof(buf), "<%i> %s", (LOG_AUTH | LOG_INFO), message);


status = sendto(sock, buf, strlen(buf) , 0,(struct sockaddr *) &name,sizeof name);


close(sock);
if (status == -1)
return 1;


return 0;
}




/*
** NAME:
** get_user_data()
**
** DESCRIPTION:
** prints PAM parameters into buf.
**
** INPUTS:
** pamh: pam handle
** buf: buffer to save data
** length: size of the buffer
** extra: if (extra == 0) then fewer pramameter are copied into buf
**
** RETURNS:
** allways returns buf
**
** CALLS:
**
** CALLED BY:
** pam_sm_authenticate()
** pam_sm_open_session()
** pam_sm_close_session()
**
** SIDE EFFECTS:
*/


char* get_user_data(pam_handle_t *pamh, char* buf, size_t length, int extra)
{
char *user_name;
char *tty_name;
struct spwd *shadow;
char *service;
char *remote_host;


glob_pamh = pamh;


if (pam_get_item(pamh, PAM_USER, (const void **)&user_name) != PAM_SUCCESS)
{
user_name = "UNKNOWN_USER";
}


if (pam_get_item(pamh, PAM_TTY, (const void **)&tty_name) != PAM_SUCCESS)
{
tty_name = "UNKNOWN_TTY";
}


if (pam_get_item(pamh, PAM_SERVICE, (const void **)&service) != PAM_SUCCESS)
{
service = "UNKNOWN_SERVICE";
}


if (pam_get_item(pamh, PAM_RHOST, (const void **) &remote_host) != PAM_SUCCESS)
{
remote_host = "UNKNOWN_HOST";
}


if (extra) {
snprintf(buf, length,
"tty: %s; pid: %i; ppid: %i; service: %s; remote_host: %s; user: %s",
(tty_name == NULL) ? "NULL" : tty_name,
getpid(), getppid(),
(service == NULL) ? "NULL" : service,
(remote_host == NULL) ? "NULL" : remote_host,
(user_name == NULL) ? "NULL" : user_name);
} else {
snprintf(buf, length,
"tty: %s; pid: %i; ppid: %i; service: %s",
(tty_name == NULL) ? "NULL" : tty_name,
getpid(), getppid(),
(service == NULL) ? "NULL" : service);
}


return buf;
}


/*
** NAME:
** all the following functions, are just place holders
**
** DESCRIPTION:
**
** INPUTS:
**
** RETURNS:
**
** CALLS:
**
** CALLED BY:
**
** SIDE EFFECTS:
*/




int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
char msg[1024];
char buf[1024];
char * user_name;


if (pam_get_item(pamh, PAM_USER, (const void **)&user_name) != PAM_SUCCESS)
user_name = NULL;


if (user_name == NULL) {
snprintf(msg,
sizeof(msg),
"PSP_Auth: Unknown user during Acct Mgmt; %s",
get_user_data(pamh, buf, sizeof(buf), 1));


syslog(LOG_AUTHPRIV | LOG_INFO, "%s", msg);
return PAM_USER_UNKNOWN;
}


load_config(user_name);
if (permitted2login(pamh, user_name) == 0)
return PAM_PERM_DENIED;


glob_pamh = pamh;
return PAM_SUCCESS;
}




int pam_sm_chauthtok(pam_handle_t *pamh, const int flags, int argc, const char ** argv)
{
glob_pamh = pamh;
return _pam_sm_chauthtok(pamh, flags, argc, argv);


}


int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
{
glob_pamh = pamh;
return PAM_SUCCESS;
}


const char *
MapUser(const char *user)
{
const char * env;
static char mapped_usr[64];


env = pam_getenv(glob_pamh, "PAM_CID");
if (env == NULL || *env == 0)
env="OS";


snprintf(mapped_usr, sizeof(mapped_usr), "%s[%s]", user, env);
return mapped_usr;
}


int build_if_list();
int free_if_list();
int socket_test(char *path, int inode);
int scan_netstat(int af, char *path, int inode);
int examine_ip(int af, char *loc, char *rem);
int hex_char_to_int(char);


struct if_list {
char name[IFNAMSIZ];


unsigned short family; /* AF_INET or AF_INET6 */


int addr[4];
int mask;


struct if_list * next;
};


struct if_list * if_root;
int fab_cache = -1;


/*
* Examine all FDs for this process. Return 1 if one of those FDs
* is associated with a socket that originated from another blade
* on this system.
*/ 
int
from_same_subnet()
{
char buff[64];
char * bn;
DIR * dp;
struct dirent * dirp;
int rc;
struct stat st;


if (test_flag & IGNORE_SUBNET_CHECK)
return 0;


if (fab_cache != -1)
return fab_cache;


build_if_list();


sprintf(buff, "/proc/%d/fd", getpid());
dp = opendir(buff);
if (dp == NULL) {
free_if_list();
return 0;
}


bn = buff + strlen(buff);
*bn++ = '/';


while (dirp = readdir(dp)) {
if (strcmp(dirp->d_name, ".") == 0 ||
   strcmp(dirp->d_name, "..") == 0)
continue;


strcpy(bn, dirp->d_name);
stat(buff, &st);
if (S_ISSOCK(st.st_mode)) {
rc = socket_test(buff, st.st_ino);
if (rc) {
fab_cache = 1;
closedir(dp);
free_if_list();
return 1;
}
}
}


fab_cache = 0;
closedir(dp);
free_if_list();
return 0;
}


/*
* build a list of network interfaces with their
* address and netmask.
*/
int
build_if_list()
{
int fd;
struct ifconf ifc;
struct ifreq addr;
struct ifreq mask;
struct ifreq * ifr_ptr;
struct ifreq * ifr_end;
int rc;


struct if_list *lp;


struct sockaddr_in * addr_v4;
struct sockaddr_in * mask_v4;


struct sockaddr_in6 * addr_v6;
struct sockaddr_in6 * mask_v6;


char msg[512];


FILE * fp;
char buff[512];
char * tok;
char * delim = " \n";
char * savptr;


fd = socket(PF_INET, SOCK_DGRAM, 0);
if (fd == -1) {
snprintf(msg, sizeof(msg), "PSP_Auth: build_if_list: unable to open SOCK_DGRAM, errno: %d", errno);
syslog(LOG_AUTHPRIV | LOG_ERR, msg);
return 1;
}


/*
*  determine how much space is needed
*/
ifc.ifc_buf = NULL;
ifc.ifc_len = 0;
rc = ioctl(fd, SIOCGIFCONF, &ifc);
if (rc == -1) {
close(fd);
return 0;
}


ifc.ifc_buf = (char *)malloc(ifc.ifc_len);
rc = ioctl(fd, SIOCGIFCONF, &ifc);
if (rc == -1) {
free(ifc.ifc_buf);
close(fd);
return 0;
}


/*
* parse through the buffer
*/
ifr_ptr = (struct ifreq *)ifc.ifc_buf;
ifr_end = (struct ifreq *)(ifc.ifc_buf + ifc.ifc_len);
while (ifr_ptr < ifr_end) {
strcpy(addr.ifr_name, ifr_ptr->ifr_name);
rc = ioctl(fd, SIOCGIFADDR, &addr);
if (rc == -1) {
ifr_ptr ++;
continue;
}


strcpy(mask.ifr_name, ifr_ptr->ifr_name);
rc = ioctl(fd, SIOCGIFNETMASK, &mask);
if (rc == -1) {
ifr_ptr ++;
continue;
}


if (ifr_ptr->ifr_addr.sa_family != AF_INET) {
snprintf(msg, sizeof(msg), "PSP_Auth: build_if_list: Unexpected family (%d) from SIOCIFCONF", ifr_ptr->ifr_addr.sa_family);
syslog(LOG_AUTHPRIV | LOG_ERR, msg);
ifr_ptr ++;
continue;
}


addr_v4 = (struct sockaddr_in *)&(addr.ifr_addr);
mask_v4 = (struct sockaddr_in *)&(mask.ifr_netmask);


lp = (struct if_list *)malloc(sizeof(struct if_list));
if (lp) {
strcpy(lp->name, ifr_ptr->ifr_name);
lp->family  = AF_INET;
lp->addr[0] = ntohl(addr_v4->sin_addr.s_addr);
lp->mask    = count_bits(ntohl(mask_v4->sin_addr.s_addr));
lp->next    = if_root;
if_root     = lp;


} else {
snprintf(msg, sizeof(msg), "PSP_Auth: build_if_list: unable to allocate memory for interface %s", ifr_ptr->ifr_name);
syslog(LOG_AUTHPRIV | LOG_ERR, msg);
}


ifr_ptr ++;
}

close(fd);
free(ifc.ifc_buf);


/*
* now parse through the IPv6 addresses. The format of /proc/net/if_inet6 is:
* addr idx prefix_len scope flags name
*/
fp = fopen("/proc/net/if_inet6", "r");
if (fp == NULL) {
snprintf(msg, sizeof(msg), "PSP_Auth: build_if_list: unable to open if_inet6");
syslog(LOG_AUTHPRIV | LOG_ERR, msg);
return 0;
}


while (fgets(buff, sizeof(buff), fp) != NULL) {
lp = (struct if_list *)malloc(sizeof(struct if_list));
if (lp == NULL) {
snprintf(msg, sizeof(msg), "PSP_Auth: build_if_list: unable to allocate memory for if_inet6");
syslog(LOG_AUTHPRIV | LOG_ERR, msg);
continue;
}


tok = strtok_r(buff, delim, &savptr); /* addr */
inetx_atoi(AF_INET6, lp->addr, tok, 0);


(void) strtok_r(NULL, delim, &savptr); /* idx */


tok = strtok_r(NULL, delim, &savptr); /* prefix_len */
lp->mask = atoi(tok);


(void) strtok_r(NULL, delim, &savptr); /* scope */
(void) strtok_r(NULL, delim, &savptr); /* flags */


tok = strtok_r(NULL, delim, &savptr); /* name */
strcpy(lp->name, tok);


lp->family = AF_INET6;
lp->next   = if_root;
if_root    = lp;
}


fclose(fp);
return 0;
}


int
free_if_list()
{
struct if_list *lp;
struct if_list *tmp;


lp = if_root;
while (lp != NULL) {
tmp = lp->next;
free(lp);

lp = tmp;
}


if_root = NULL;
return 0;
}


/*
* Find the specified socket inode in one of the netstat files. 
* Return 1 if the src and dst IP is on the same subnet.
*/
int
socket_test(char *path, int inode)
{
int rc;


rc = scan_netstat(AF_INET, "/proc/net/tcp", inode);


if (rc == 0)
rc = scan_netstat(AF_INET, "/proc/net/udp", inode);


if (rc == 0)
rc = scan_netstat(AF_INET6, "/proc/net/tcp6", inode);


if (rc == 0)
rc = scan_netstat(AF_INET6, "/proc/net/udp6", inode);


return rc;
}


/*
* The format of a netstat file is:
* slot loc_addr:port rem_addr:port st tx_queue:rx_queue tr:tm->when retrnsmt uid timeout inode
*/
int
scan_netstat(int af, char *path, int inode)
{
FILE * fp;
char buff[256];
char * loc;
char * rem;
char * ino;
char * delim = " ";
char * savptr;
int rc;


fp = fopen(path, "r");
if (fp == NULL)
return 0;


while (fgets(buff, sizeof(buff), fp) != NULL) {
(void) strtok_r(buff, delim, &savptr); /* slot */
loc  = strtok_r(NULL, delim, &savptr); /* loc_addr:port */
rem  = strtok_r(NULL, delim, &savptr); /* rem_addr:port */
(void) strtok_r(NULL, delim, &savptr); /* st */
(void) strtok_r(NULL, delim, &savptr); /* tx_queue:rx_queue */
(void) strtok_r(NULL, delim, &savptr); /* tr:tm->when */
(void) strtok_r(NULL, delim, &savptr); /* retrnsmt */
(void) strtok_r(NULL, delim, &savptr); /* uid */
(void) strtok_r(NULL, delim, &savptr); /* timeout */
ino  = strtok_r(NULL, delim, &savptr); /* inode */


if (atoi(ino) == inode && examine_ip(af, loc, rem)) {
fclose(fp);
return 1;
}
}


fclose(fp);
return 0;
}


/*
* Convert the "netstat" ascii address into 32 bit values(s)
*/
int
inetx_atoi(int af, int *lp, char *adr, int swap)
{
int tmp;


tmp = hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
lp[0] = (swap) ? ntohl(tmp) : tmp;


if (af == AF_INET6) {
tmp = hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
lp[1] = swap ? ntohl(tmp) : tmp;


tmp = hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
lp[2] = swap ? ntohl(tmp) : tmp;


tmp = hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
tmp = (tmp << 4) + hex_char_to_int(*adr++);
lp[3] = swap ? ntohl(tmp) : tmp;
}


return 1;
}


/*
* Return 1 if loc and rem are in the same subnet
*/
int
examine_ip(int af, char *loc, char *rem)
{
int l[4];
int r[4];
int l_mapped;
int r_mapped;
struct if_list *lp;


inetx_atoi(af, l, loc, 1);
l_mapped = 0;
if (af == AF_INET6 && l[2] == 0xFFFF) {
l_mapped = 1;
}


inetx_atoi(af, r, rem, 1);
r_mapped = 0;
if (af == AF_INET6 && r[2] == 0xFFFF) {
r_mapped = 1;
}


if (af == AF_INET6 && l_mapped && r_mapped) {
// IPv4-mapped addresses
l[0] = l[3];
r[0] = r[3];


af = AF_INET;
}


if (af == AF_INET) {
// IPv4 comparison
lp = if_root;
while (lp) {
if (lp->addr[0] == l[0]) {
break;
}


lp = lp->next;
}


if (lp == NULL) {
return 0;
}


// See if l[0] and r[0] are in the same subnet
if (same_subnet(l, r, lp->mask)) {
return 1;
}


return 0;
}


if (af == AF_INET6) {
// IPv6 comparison
lp = if_root;
while (lp) {
if (lp->addr[0] == l[0] &&
   lp->addr[1] == l[1] &&
   lp->addr[2] == l[2] &&
   lp->addr[3] == l[3]) {
break;
}


lp = lp->next;
}


if (lp == NULL) {
return 0;
}

// See if l[0] and r[0] are in the same subnet
if (same_subnet(l, r, lp->mask)) {
return 1;
}


return 0;
}


return 0;
}


// Count the  number of bits in an IPv4 subnet mask, making
// it consistent with the IPv6 prefix_len.
int
count_bits(int val)
{
int bits;
int cnt = 32;


bits = 0;
while (cnt) {
if (val & 1)
bits++; 


val >>= 1;
cnt--;
}


return bits;
}


// Return 1 if the leading number of bits in loc and rem match
int
same_subnet(int *loc, int *rem, int bits)
{
int vloc;
int vrem;
int mask;
int shift;


while (bits >= 32) {
if (*loc != *rem)
return 0;


bits -= 32;
loc++;
rem++;
}


// bits < 32
if (bits) {
/*
* convert bits to mask
*/
mask = 0;
shift = 32 - bits - 1;
while (bits) {
mask |= 1;
mask <<= 1;


bits--;
}
mask <<= shift;


vloc = *loc;
vrem = *rem;
if ((vloc & mask) != (vrem & mask)) {
return 0;
}
}


return 1;
}


int
hex_char_to_int(char ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';


if (ch >= 'A' && ch <= 'F')
return (ch - 'A' + 10);


if (ch >= 'a' && ch <= 'f')
return (ch - 'a' + 10);

return -1;
}

阅读(4251) | 评论(0) | 转发(0) |
0

上一篇:常见问题

下一篇:App example using PAM

给主人留下些什么吧!~~