Files
seta75D d6fe8fe829 Init
2021-10-11 22:19:34 -03:00

778 lines
22 KiB
C

static char sccsid[] = "@(#)11 1.6 src/bos/usr/ccs/lib/libs/authenticate.c, libs, bos41J, 9519A_all 5/8/95 14:19:32";
/*
* COMPONENT_NAME: LIBS
*
* FUNCTIONS: authenticate
* _method_authenticate
* _grammar_authenticate
* _crypt_authenticate
* _run_parse_tree
* _set_authenv
* _pwddup
* _pwdfree
* SET_AUTHSTATE
* RESET
* END
* CONTINUE
*
* ORIGINS: 27
*
* IBM CONFIDENTIAL -- (IBM Confidential Restricted when
* combined with the aggregated modules for this product)
* SOURCE MATERIALS
*
* (C) COPYRIGHT International Business Machines Corp. 1993, 1995
* All Rights Reserved
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*/
#include <sys/errno.h>
#include <usersec.h> /* user attributes */
#include <userpw.h>
#include <userconf.h> /* security configuration attributes */
#include <syslog.h>
#include <login.h>
#include <string.h>
#include <pwd.h>
#include "libs.h"
/*
* Prototypes, internal and external
*/
int _crypt_authenticate(char *, char *, char *);
struct passwd * _pwddup(struct passwd *);
void _pwdfree(struct passwd *);
static void _set_authenv(char *);
static int _grammar_authenticate(char *, char *, MsgBuf *, struct passwd *,
char *);
static int _run_parse_tree(char *, char *, MsgBuf *, struct passwd *,
struct secgrammar_tree *,struct methodstats **);
static char * _method_authenticate(char *, char *, MsgBuf *, struct passwd *,
char *, int, struct methodstats **);
extern struct secgrammar_tree * _create_parse_tree(char *);
extern void _release_parse_tree(struct secgrammar_tree *);
/*
* The following structure definition is used to hold the name and
* return code of each method checked. Since method names may appear more
* than once in a grammar list, there is no reason to check them again after
* the first time. I will keep a list of checked methods for referral.
*/
struct methodstats
{
char *name;
int returned;
struct methodstats *next;
};
/*
* The authstate variable describes the primary authentication method
* that passed during login (this is defined as the first method that passes).
* The authstate variable is used to hold the method name of any authentication
* routines that pass (either _method_authenticate() or _crypt_authenticate()).
* The SET_AUTHSTATE() macro initializes the variable, and the _set_authenv()
* routine which places authstate into the environment.
*/
static char *authstate;
#define SET_AUTHSTATE(a)\
{\
if (!authstate) authstate = strdup(a);\
}
/*
* Macro to reset internal static variables.
*/
#define RESET\
{\
state = INITIAL;\
if (fname) { free(fname); fname = (char *)NULL; }\
if (authstate) { free(authstate); authstate = (char *)NULL; }\
if (grammar) { free(grammar); grammar = (char *)NULL; }\
if (pwd) { _pwdfree(pwd); pwd = \
(struct passwd *)NULL; }\
}
/*
* Macro to end processing and reset internal variables. After
* this is called authenticate() is ready to process a new user.
* Set the authstate if the user passed authentication.
*/
#define END(a)\
{\
*reenter = 0;\
if (!a) _set_authenv(authstate);\
RESET;\
return(a);\
}
/*
* Macro to return to the caller with expectations of a recall
* to authenticate(). On reentry to this routine, authentication
* will be in state "b" (QUERY).
*/
#define CONTINUE(b)\
{\
*reenter = 1;\
state = b;\
return(0);\
}
/*
* NAME: authenticate
*
* FUNCTION: Entry point for all system authentication. This routine
* implements a recallable interface that provides challenges
* and validates responses. The routine may return with a
* challenge to the user in the "message" parameter. If the
* "reenter" variable is set, then the caller of this routine
* should respond to the challenge with a recall and response
* in the "passwd" parameter.
*
* RETURNS: 0 && (reenter == 0) - user has passed authentication.
* 0 && (reenter == 1) - user must respond to challenge.
* 1 && (reenter == 0) - user has failed authentication.
*
* ERRNO:
* ENOENT - User does not exist
* ENOMEM - Malloc failed
* ESAD - Authentication denial
* EINVAL - Invalid parameter, invalid grammar, or invalid
* method authentication function
*/
int
authenticate(char *uname, char *passwd, int *reenter, char **message)
{
static enum {INITIAL, QUERY} state;
static struct passwd *pwd; /* User's passwd structure */
static char *fname; /* User name upon first entry */
static char *grammar; /* Grammar to authenticate by */
int rc; /* Return code */
char shortname[PW_NAMELEN]; /* Short name (max 8 bytes) */
MsgBuf mb;
/* The caller must supply a valid user name */
if (!uname || (uname[0] == (char)NULL))
{
errno = EINVAL;
END(1);
}
/*
* If I currently have a user name from a previous call and am
* now being given a different user name then I should reset.
*/
if (fname && strcmp(fname, uname))
RESET;
_MBInitialize(&mb, message); /* Set up for return messages */
switch (state)
{
case INITIAL:
/*
* Malloc and store full user name from initial call.
* This will be rechecked upon reentry.
*/
if ((fname = strdup(uname)) == (char *)NULL)
END(1);
/* Retrieve possibly shortened AIX specification for name */
_normalize_username(shortname, uname);
setpwent();
/*
* Check the name resolution to see if this user is defined.
* If the user is unknown then we fail authentication without
* ever checking the password.
*/
if ((pwd = getpwnam(uname)) == (struct passwd *)NULL)
{
_MBReplace(&mb, MSGSTR(M_USRNONEX, DEF_USRNONEX),
uname);
errno = ENOENT;
END(1);
}
/* Get local copy so any subsequent operations don't lose it. */
if ((pwd = _pwddup(pwd)) == (struct passwd *)NULL)
END(1);
endpwent();
setuserdb(S_READ);
/*
* Retrieve "SYSTEM" line from the database. This will
* describe the authentication domains for this user. If
* the SYSTEM line does not appear then this implicitly means
* local password authentication (includes NIS).
*/
getuserattr(shortname,S_AUTHSYSTEM, (void *)&grammar, SEC_CHAR);
/* Assign default or convert to correct moniker */
if ((grammar == (char *)NULL) || (grammar[0] == (char)NULL))
grammar = AUTH_COMPAT;
else if (_grammar_keyword(grammar, &grammar) == -1)
{
grammar = (char *)NULL;
END(1);
}
/* Get local copy so any subsequent operations don't lose it. */
if ((grammar = strdup(grammar)) == (char *)NULL)
END(1);
enduserdb();
/*
* If the grammar explicitly states no authentication required
* then we exit successfully.
*/
if (ISNONE(grammar))
{
SET_AUTHSTATE(AUTH_COMPAT);
END(0);
}
/*
* If the user has no password and supplied no password
* during the initial invocation, then authentication passes.
*/
if ( (pwd->pw_passwd == (char *)NULL ||
pwd->pw_passwd[0] == (char)NULL)
&& (!passwd || passwd[0] == (char)NULL))
{
SET_AUTHSTATE(AUTH_COMPAT);
END(0);
}
/*
* If the user supplied no passwd argument during the
* initial call then move to the query phase.
*/
if (!passwd)
{
if (_MBReplace(&mb,
MSGSTR(M_PWPROMPT, DEF_PWPROMPT), uname))
END(1);
CONTINUE(QUERY); /* Next phase is query */
}
/* ... Otherwise continue into the query phase */
/*
* If I have just queried for the password I will call
* the authentication routines.
*/
case QUERY:
/*
* If the grammar is a simple local or compat authentication
* (normal case which uses crypt()/compare authentication)
* then I will call it directly. Otherwise I'll move into
* the longer approach and parse the grammar.
*/
if (ISLOCAL(grammar) || ISCOMPAT(grammar))
{
rc = _crypt_authenticate(grammar, uname, passwd);
if (rc == AUTH_SUCCESS)
{
SET_AUTHSTATE(grammar);
}
else
_MBReplace(&mb, MSGSTR(M_FAILLOCAL,
DEF_FAILLOCAL), pwd->pw_name);
}
else
rc = _grammar_authenticate(uname, passwd, &mb, pwd,
grammar);
END(rc);
}
}
/*
* NAME: _crypt_authenticate
*
* FUNCTION: This function determines if the user passes normal AIX style
* authentication. This authentication is based on encrypted
* passwords.
*
* Both NIS and local system users authenticate in this fashion.
*
*
* RETURNS:
* AUTH_SUCCESS - authentication passed
* AUTH_FAILURE - authentication failed.
*
* ERRNO: ESAD - Authentication failed
*/
int
_crypt_authenticate(char *method,
char *uname, /* User's name */
char *passwd) /* User's cleartext password */
{
char lpasswd[PW_PASSLEN+1];
char *tp; /* temporary pointer */
int rc = AUTH_FAILURE; /* return code */
struct passwd *pwd = NULL; /* for getpwnam() */
struct passwd *tmp_pwd = NULL; /* for getpwnam() */
/*
* Call getpwnam to get the user's passwd struct, restricting
* getpwnam lookups to the local AIX files (including NIS).
* This routine is performing a traditional authentication
* because SYSTEM is set to "compat" or "files". So the user's
* password information must be found traditionally as well (either
* local or NIS, but not DCE). AUTHSTATE cannot be relied on here.
*/
set_getpwent_remote(2); /* local lookups only */
tmp_pwd = getpwnam(uname);
set_getpwent_remote(1); /* resume full lookups */
if (tmp_pwd)
pwd = _pwddup(tmp_pwd);
if ( !tmp_pwd || !pwd )
{
rc = AUTH_FAILURE;
errno = ESAD;
return(rc);
}
/*
* If the user possesses a password then crypt() and compare.
* Otherwise if the password is NULL and the user supplied
* some form of password, consider that failing as well.
*/
if (pwd->pw_passwd && pwd->pw_passwd[0])
{
if (passwd && (strlen(passwd) > PW_PASSLEN))
{
/*
* Truncate password to 8 characters.
*/
passwd = strncpy(lpasswd, passwd, PW_PASSLEN);
passwd[PW_PASSLEN] = '\0';
}
tp = crypt(passwd, pwd->pw_passwd);
/* if passwd doesn't match then authentication fails */
if (!strcmp(pwd->pw_passwd, tp))
rc = AUTH_SUCCESS;
}
else
{
if (passwd == NULL || passwd[0] == (char)NULL)
rc = AUTH_SUCCESS;
}
/*
* If I pass the crypt and compare, and am trying to pass
* local authentication only, then I should not be an NIS user.
* This case should be very rare since the default grammar is
* AUTH_COMPAT. AUTH_FILES would be kind of a silly way to cut off
* NIS users when you have the "+/-" syntax in the /etc/passwd file.
*/
if ((rc == AUTH_SUCCESS) && ISLOCAL(method))
{
/* If user is not explicitly in local database then we fail */
if (!_is_user_in_local_database(pwd->pw_name))
rc = AUTH_FAILURE;
}
if (rc == AUTH_FAILURE) errno = ESAD;
_pwdfree(pwd);
return(rc);
}
/*
* NAME: _grammar_authenticate
*
* FUNCTION: This function accepts a user name, a password, and a grammar with
* which to authenticate the user. This grammar is used to create
* a parse tree which is then evaluated bottom up and left to right.
*
* RETURNS: 0 - User may login
* 1 - User may not login
*
* ERRNO: EINVAL - Invalid parse tree
*/
static int
_grammar_authenticate(char *uname, /* User name */
char *passwd, /* User's cleartext password */
MsgBuf *mb, /* Message to return to user */
struct passwd *pwd, /* User's passwd structure */
char *grammar) /* Authentication grammar */
{
struct methodstats *headmethod = (struct methodstats *)NULL;
/* Head element in a linked list */
/* of currently evaluated methods */
struct methodstats *front; /* Used during list release */
struct secgrammar_tree *tptr; /* Parse tree root pointer */
int rc; /* return code */
/*
* Create a parse tree for the grammar. If the parse tree has
* a syntax error and cannot be produced, then log the error and
* perform local authentication. Otherwise run the parse tree
* and release it.
*/
if((tptr=_create_parse_tree(grammar)) == (struct secgrammar_tree *)NULL)
{
if (_MBReplace(mb,
MSGSTR(M_GRAMMARERR, DEF_GRAMMARERR), grammar))
return(AUTH_FAILURE);
rc = _crypt_authenticate(AUTH_COMPAT, uname, passwd);
if (rc == AUTH_SUCCESS)
{
SET_AUTHSTATE(AUTH_COMPAT);
}
else
_MBAppend(mb, MSGSTR(M_FAILLOCAL, DEF_FAILLOCAL),
pwd->pw_name);
}
else
{
rc = _run_parse_tree(uname,passwd, mb, pwd, tptr, &headmethod);
_release_parse_tree(tptr);
/*
* Release the linked list possibly created during the
* parse tree run
*/
while (headmethod)
{
front = headmethod->next;
if (headmethod->name)
free(headmethod->name);
free(headmethod);
headmethod = front;
}
}
return(rc);
}
/*
* NAME: _run_parse_tree
*
* FUNCTION: This function implements a recursive, left, right, evaluation
* of the grammar parse tree.
*
* RETURNS: 0 - User may login
* 1 - User may not login
*/
static int
_run_parse_tree(char *uname, /* User name */
char *passwd, /* User's cleartext password */
MsgBuf *mb, /* Message to return to user */
struct passwd *pwd, /* User's passwd structure */
struct secgrammar_tree *tptr, /* Node of parse tree */
struct methodstats **headmethod)/* List of evaluated methods */
{
int rc = AUTH_FAILURE, /* return code from _run_parse_tree() */
lrc = AUTH_FAILURE, /* left branch return code */
rrc = AUTH_FAILURE; /* right branch return code */
/*
* Evaluation specific cases for interior nodes of the tree
* and for leaves of the tree.
*/
switch (tptr->type)
{
case LEAF:
rc = _method_authenticate(uname, passwd, mb, pwd,
tptr->name, tptr->result, headmethod);
break;
case NODE:
if (tptr->left) /* Visit left branch first */
lrc = _run_parse_tree(uname, passwd, mb,
pwd, tptr->left, headmethod);
/*
* Once the left branch is visited, we will use
* short circuit evaluation to determine if the
* right branch need be visited. If the operator
* is "AND" and the left branch evaluated false
* then no need to visit right branch. If the
* operator is "OR" and the left branch evaluated
* true then no need to visit right branch.
*/
if (!strcmp(tptr->operator, AUTH_AND) &&
(lrc == AUTH_FAILURE))
return(AUTH_FAILURE);
if (!strcmp(tptr->operator, AUTH_OR) &&
(lrc == AUTH_SUCCESS))
return(AUTH_SUCCESS);
if (tptr->right) /* Otherwise visit right branch */
rrc = _run_parse_tree(uname, passwd, mb,
pwd, tptr->right, headmethod);
/* Finally evaluate return codes. */
if (!strcmp(tptr->operator, AUTH_AND))
{
if(rrc == AUTH_SUCCESS)
rc = AUTH_SUCCESS;
}
else if (!strcmp(tptr->operator, AUTH_OR))
{
if(rrc == AUTH_SUCCESS)
rc = AUTH_SUCCESS;
}
break;
}
return(rc);
}
/*
* NAME: _method_authenticate()
*
* FUNCTION: This method either calls a method authentication function,
* or discerns what a previous call to the method returned.
* Once the return code from the method is ascertained, it
* is compared against the expected result. If the return
* code matches the result specified in the grammar, then
* the function returns success.
*
* RETURNS: 0 - Method return code matches expected result
* 1 - Method return code does not match expected result.
*
* ERRNO: EINVAL - Invalid method or method function
* ESAD - Authentication failed
*/
static char *
_method_authenticate
(char *uname, /* User name */
char *passwd, /* User's cleartext password */
MsgBuf *mb, /* Message to return to user */
struct passwd *pwd, /* User's passwd structure */
char *method, /* Method name to evaluate */
int result, /* Expected result of method */
struct methodstats **headmethod)/* List of evaluated methods */
{
struct secmethod_table methodtable;
struct methodstats *newptr,
*ptr = *headmethod;
char *msg = (char *)NULL;/* Return message from load method */
int rc;
int dummy; /* unused field in case we want to export */
/* challenges and continue into the methods */
/* at a future date. */
/*
* Performance objective here is to check the return codes
* from previous method invocations since method names may
* appear in a grammar more than once, and there is no reason
* to recall them.
*/
while (ptr)
{
if (!strcmp(method, ptr->name))
{
if ((result == AUTH_WILDCARD) ||
(ptr->returned == result))
{
if (result == AUTH_SUCCESS)
SET_AUTHSTATE(method);
return(AUTH_SUCCESS);
}
else
return(AUTH_FAILURE);
}
ptr = ptr->next;
}
/*
* If we have gotten here, we can assume that we have not called
* this method before. We must malloc space to record this call
* so that it need not be loaded if we see this method again.
*/
if ((newptr = (struct methodstats *)
malloc(sizeof(struct methodstats))) == (struct methodstats *)NULL)
return(AUTH_FAILURE);
/*
* Initialize the structure, set the returned field to failure and
* place at front of list.
*/
memset(newptr, 0, sizeof(struct methodstats));
newptr->returned = AUTH_FAILURE;
/*
* malloc space for the name and record it
*/
if ((newptr->name = strdup(method)) == (char *)NULL)
return(AUTH_FAILURE);
newptr->next = *headmethod;
*headmethod = newptr;
/*
* If the method is local or compat then use normal crypt()/compare
* authentication. Call the local routines and connect the method
* up to the list of checked methods.
*/
if (ISLOCAL(method) || ISCOMPAT(method))
{
newptr->returned = _crypt_authenticate(method, uname, passwd);
if ((result == AUTH_WILDCARD) || (newptr->returned == result))
{
if (result == AUTH_SUCCESS)
SET_AUTHSTATE(method);
return(AUTH_SUCCESS);
}
else
{
_MBAppend(mb, MSGSTR(M_FAILLOCAL, DEF_FAILLOCAL),
pwd->pw_name);
return(AUTH_FAILURE);
}
}
/*
* Otherwise load the security method and if it has defined an
* authenticate entry point then call it.
*/
if (_load_secmethod(newptr->name, &methodtable))
{
_MBAppend(mb, MSGSTR(M_FAILLOAD, DEF_FAILLOAD), newptr->name);
return(AUTH_FAILURE);
}
if (methodtable.method_authenticate == NULL)
{
errno = EINVAL;
_MBAppend(mb, MSGSTR(M_NOAUTHPTR, DEF_NOAUTHPTR),newptr->name);
return(AUTH_FAILURE);
}
newptr->returned = (*methodtable.method_authenticate)
(uname,passwd,&dummy,&msg);
/*
* Concatenate this message (if present) onto our return messages
* and free the storage allocated in the method.
*/
if (msg)
{
if (_MBAppend(mb, msg))
{
free(msg);
return(AUTH_FAILURE);
}
free(msg);
}
if ((result == AUTH_WILDCARD) || (newptr->returned == result))
{
if (result == AUTH_SUCCESS)
SET_AUTHSTATE(method);
return(AUTH_SUCCESS);
}
errno = ESAD;
return(AUTH_FAILURE);
}
/*
* NAME: _set_authenv
*
* FUNCTION: The AUTHSTATE environment variable is set during
* authentication and describes the primary authentication
* mechanism.
*
* RETURNS: void
*/
static void
_set_authenv(char *state)
{
char *envbuf;
if ((envbuf = (char *)malloc(sizeof("AUTHSTATE=") +
strlen(state))) == (char *)NULL)
return;
strcpy(envbuf, "AUTHSTATE=");
strcat(envbuf, state);
putenv(envbuf);
reset_grjunk_authstate();
reset_pwjunk_authstate();
}
/*
* NAME: _pwddup
*
* FUNCTION: Malloc and store a local copy of the passwd structure.
* This is important since the getpw* routines have static
* storage that is rewritten ever time a new lookup is done.
* If subsequent operations are possible using these routines
* and you are dependent on your copy of the passwd structure,
* then your copy should be saved in local storage.
*
* The free routine for this storage is _pwdfree()
*
* RETURNS: NULL - malloc failure
* struct passwd * - newly allocated copy of passwd structure
*/
struct passwd *
_pwddup(struct passwd *pwd)
{
struct passwd *newpw;
if ((newpw = (struct passwd *)malloc(sizeof(struct passwd))) ==
(struct passwd *)NULL)
return((struct passwd *)NULL);
memset(newpw, 0, sizeof(struct passwd));
if (((newpw->pw_name = strdup(pwd->pw_name)) == (char *)NULL) ||
((newpw->pw_passwd = strdup(pwd->pw_passwd)) == (char *)NULL) ||
((newpw->pw_gecos = strdup(pwd->pw_gecos)) == (char *)NULL) ||
((newpw->pw_dir = strdup(pwd->pw_dir)) == (char *)NULL) ||
((newpw->pw_shell = strdup(pwd->pw_shell)) == (char *)NULL))
{
if (newpw->pw_name) free(newpw->pw_name);
if (newpw->pw_passwd) free(newpw->pw_passwd);
if (newpw->pw_gecos) free(newpw->pw_gecos);
if (newpw->pw_dir) free(newpw->pw_dir);
if (newpw->pw_shell) free(newpw->pw_shell);
free(newpw);
return((struct passwd *)NULL);
}
newpw->pw_uid = pwd->pw_uid;
newpw->pw_gid = pwd->pw_gid;
return(newpw);
}
/*
* NAME: _pwdfree
*
* FUNCTION: Frees malloc'd storage allocated when making a local copy
* of the passwd structure.
*
* RETURNS: Nothing
*/
void
_pwdfree(struct passwd *pwd)
{
if (!pwd) return;
if (pwd->pw_name) free(pwd->pw_name);
if (pwd->pw_passwd) free(pwd->pw_passwd);
if (pwd->pw_gecos) free(pwd->pw_gecos);
if (pwd->pw_dir) free(pwd->pw_dir);
if (pwd->pw_shell) free(pwd->pw_shell);
free(pwd);
}