1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-28 07:21:54 +03:00
samba-mirror/source/passdb/pass_check.c
Tim Potter 36d7cb4ccc Moved code that changes the pw_passwd entry (i.e shadow password and
weird unixware stuff) into _Get_Pwnam() to fix a memory allocation bug.

Note that the Get_Pwnam() function now returns a const struct passwd *
as a hint to other developers not to change entries in the struct
passwd.
0001-01-01 00:00:00 +00:00

855 lines
22 KiB
C

/*
Unix SMB/Netbios implementation.
Version 1.9.
Password checking
Copyright (C) Andrew Tridgell 1992-1998
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* this module is for checking a username/password against a system
password database. The SMB encrypted password support is elsewhere */
#include "includes.h"
extern int DEBUGLEVEL;
/* these are kept here to keep the string_combinations function simple */
static char this_user[100]="";
static char this_salt[100]="";
static char this_crypted[100]="";
#ifdef HAVE_PAM
/*******************************************************************
check on PAM authentication
********************************************************************/
/* We first need some helper functions */
#include <security/pam_appl.h>
/* Static variables used to communicate between the conversation function
* and the server_login function
*/
static char *PAM_username;
static char *PAM_password;
/* PAM conversation function
* Here we assume (for now, at least) that echo on means login name, and
* echo off means password.
*/
static int PAM_conv (int num_msg,
const struct pam_message **msg,
struct pam_response **resp,
void *appdata_ptr) {
int replies = 0;
struct pam_response *reply = NULL;
#define COPY_STRING(s) (s) ? strdup(s) : NULL
reply = malloc(sizeof(struct pam_response) * num_msg);
if (!reply) return PAM_CONV_ERR;
for (replies = 0; replies < num_msg; replies++) {
switch (msg[replies]->msg_style) {
case PAM_PROMPT_ECHO_ON:
reply[replies].resp_retcode = PAM_SUCCESS;
reply[replies].resp = COPY_STRING(PAM_username);
/* PAM frees resp */
break;
case PAM_PROMPT_ECHO_OFF:
reply[replies].resp_retcode = PAM_SUCCESS;
reply[replies].resp = COPY_STRING(PAM_password);
/* PAM frees resp */
break;
case PAM_TEXT_INFO:
/* fall through */
case PAM_ERROR_MSG:
/* ignore it... */
reply[replies].resp_retcode = PAM_SUCCESS;
reply[replies].resp = NULL;
break;
default:
/* Must be an error of some sort... */
free (reply);
return PAM_CONV_ERR;
}
}
if (reply) *resp = reply;
return PAM_SUCCESS;
}
static struct pam_conv PAM_conversation = {
&PAM_conv,
NULL
};
static BOOL pam_auth(char *user,char *password)
{
pam_handle_t *pamh;
int pam_error;
/* Now use PAM to do authentication. For now, we won't worry about
* session logging, only authentication. Bail out if there are any
* errors. Since this is a limited protocol, and an even more limited
* function within a server speaking this protocol, we can't be as
* verbose as would otherwise make sense.
* Query: should we be using PAM_SILENT to shut PAM up?
*/
#define PAM_BAIL if (pam_error != PAM_SUCCESS) { \
pam_end(pamh, 0); return False; \
}
PAM_password = password;
PAM_username = user;
pam_error = pam_start("samba", user, &PAM_conversation, &pamh);
PAM_BAIL;
/* Setting PAM_SILENT stops generation of error messages to syslog
* to enable debugging on Red Hat Linux set:
* /etc/pam.d/samba:
* auth required /lib/security/pam_pwdb.so nullok shadow audit
* _OR_ change PAM_SILENT to 0 to force detailed reporting (logging)
*/
pam_error = pam_authenticate(pamh, PAM_SILENT);
PAM_BAIL;
/* It is not clear to me that account management is the right thing
* to do, but it is not clear that it isn't, either. This can be
* removed if no account management should be done. Alternately,
* put a pam_allow.so entry in /etc/pam.conf for account handling. */
pam_error = pam_acct_mgmt(pamh, PAM_SILENT);
PAM_BAIL;
pam_end(pamh, PAM_SUCCESS);
/* If this point is reached, the user has been authenticated. */
return(True);
}
#endif
#ifdef WITH_AFS
/*******************************************************************
check on AFS authentication
********************************************************************/
static BOOL afs_auth(char *user,char *password)
{
long password_expires = 0;
char *reason;
/* For versions of AFS prior to 3.3, this routine has few arguments, */
/* but since I can't find the old documentation... :-) */
setpag();
if (ka_UserAuthenticateGeneral(KA_USERAUTH_VERSION+KA_USERAUTH_DOSETPAG,
user,
(char *) 0, /* instance */
(char *) 0, /* cell */
password,
0, /* lifetime, default */
&password_expires, /*days 'til it expires */
0, /* spare 2 */
&reason) == 0) {
return(True);
}
return(False);
}
#endif
#ifdef WITH_DFS
/*****************************************************************
This new version of the DFS_AUTH code was donated by Karsten Muuss
<muuss@or.uni-bonn.de>. It fixes the following problems with the
old code :
- Server credentials may expire
- Client credential cache files have wrong owner
- purge_context() function is called with invalid argument
This new code was modified to ensure that on exit the uid/gid is
still root, and the original directory is restored. JRA.
******************************************************************/
sec_login_handle_t my_dce_sec_context;
int dcelogin_atmost_once = 0;
/*******************************************************************
check on a DCE/DFS authentication
********************************************************************/
static BOOL dfs_auth(char *user,char *password)
{
error_status_t err;
int err2;
int prterr;
signed32 expire_time, current_time;
boolean32 password_reset;
struct passwd *pw;
sec_passwd_rec_t passwd_rec;
sec_login_auth_src_t auth_src = sec_login_auth_src_network;
unsigned char dce_errstr[dce_c_error_string_len];
if (dcelogin_atmost_once) return(False);
#ifdef HAVE_CRYPT
/*
* We only go for a DCE login context if the given password
* matches that stored in the local password file..
* Assumes local passwd file is kept in sync w/ DCE RGY!
*/
if (strcmp((char *)crypt(password,this_salt),this_crypted)) {
return(False);
}
#endif
sec_login_get_current_context(&my_dce_sec_context, &err);
if (err != error_status_ok ) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't get current context. %s\n", dce_errstr));
return(False);
}
sec_login_certify_identity(my_dce_sec_context, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't get current context. %s\n", dce_errstr));
return(False);
}
sec_login_get_expiration(my_dce_sec_context, &expire_time, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't get expiration. %s\n", dce_errstr));
return(False);
}
time(&current_time);
if (expire_time < (current_time + 60)) {
struct passwd *pw;
sec_passwd_rec_t *key;
sec_login_get_pwent(my_dce_sec_context,
(sec_login_passwd_t*)&pw, &err);
if (err != error_status_ok ) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't get pwent. %s\n", dce_errstr));
return(False);
}
sec_login_refresh_identity(my_dce_sec_context, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't refresh identity. %s\n",
dce_errstr));
return(False);
}
sec_key_mgmt_get_key(rpc_c_authn_dce_secret, NULL,
(unsigned char *)pw->pw_name,
sec_c_key_version_none,
(void**)&key, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't get key for %s. %s\n",
pw->pw_name, dce_errstr));
return(False);
}
sec_login_valid_and_cert_ident(my_dce_sec_context, key,
&password_reset, &auth_src,
&err);
if (err != error_status_ok ) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't validate and certify identity for %s. %s\n",
pw->pw_name, dce_errstr));
}
sec_key_mgmt_free_key(key, &err);
if (err != error_status_ok ) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't free key.\n", dce_errstr));
}
}
if (sec_login_setup_identity((unsigned char *)user,
sec_login_no_flags,
&my_dce_sec_context,
&err) == 0) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE Setup Identity for %s failed: %s\n",
user,dce_errstr));
return(False);
}
sec_login_get_pwent(my_dce_sec_context,
(sec_login_passwd_t*)&pw, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't get pwent. %s\n", dce_errstr));
return(False);
}
sec_login_purge_context(&my_dce_sec_context, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't purge context. %s\n", dce_errstr));
return(False);
}
/*
* NB. I'd like to change these to call something like become_user()
* instead but currently we don't have a connection
* context to become the correct user. This is already
* fairly platform specific code however, so I think
* this should be ok. I have added code to go
* back to being root on error though. JRA.
*/
if (setregid(-1, pw->pw_gid) != 0) {
DEBUG(0,("Can't set egid to %d (%s)\n",
pw->pw_gid, strerror(errno)));
return False;
}
if (setreuid(-1, pw->pw_uid) != 0) {
setgid(0);
DEBUG(0,("Can't set euid to %d (%s)\n",
pw->pw_uid, strerror(errno)));
return False;
}
if (sec_login_setup_identity((unsigned char *)user,
sec_login_no_flags,
&my_dce_sec_context,
&err) == 0) {
dce_error_inq_text(err, dce_errstr, &err2);
/* Go back to root, JRA. */
setuid(0);
setgid(0);
DEBUG(0,("DCE Setup Identity for %s failed: %s\n",
user,dce_errstr));
return(False);
}
sec_login_get_pwent(my_dce_sec_context,
(sec_login_passwd_t*)&pw, &err);
if (err != error_status_ok ) {
dce_error_inq_text(err, dce_errstr, &err2);
/* Go back to root, JRA. */
setuid(0);
setgid(0);
DEBUG(0,("DCE can't get pwent. %s\n", dce_errstr));
return(False);
}
passwd_rec.version_number = sec_passwd_c_version_none;
passwd_rec.pepper = NULL;
passwd_rec.key.key_type = sec_passwd_plain;
passwd_rec.key.tagged_union.plain = (idl_char *)password;
sec_login_validate_identity(my_dce_sec_context,
&passwd_rec, &password_reset,
&auth_src, &err);
if (err != error_status_ok ) {
dce_error_inq_text(err, dce_errstr, &err2);
/* Go back to root, JRA. */
setuid(0);
setgid(0);
DEBUG(0,("DCE Identity Validation failed for principal %s: %s\n",
user,dce_errstr));
return(False);
}
sec_login_certify_identity(my_dce_sec_context, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
/* Go back to root, JRA. */
setuid(0);
setgid(0);
DEBUG(0,("DCE certify identity failed: %s\n", dce_errstr));
return(False);
}
if (auth_src != sec_login_auth_src_network) {
DEBUG(0,("DCE context has no network credentials.\n"));
}
sec_login_set_context(my_dce_sec_context, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE login failed for principal %s, cant set context: %s\n",
user,dce_errstr));
sec_login_purge_context(&my_dce_sec_context, &err);
/* Go back to root, JRA. */
setuid(0);
setgid(0);
return(False);
}
sec_login_get_pwent(my_dce_sec_context,
(sec_login_passwd_t*)&pw, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE can't get pwent. %s\n", dce_errstr));
/* Go back to root, JRA. */
setuid(0);
setgid(0);
return(False);
}
DEBUG(0,("DCE login succeeded for principal %s on pid %d\n",
user, getpid()));
DEBUG(3,("DCE principal: %s\n"
" uid: %d\n"
" gid: %d\n",
pw->pw_name, pw->pw_uid, pw->pw_gid));
DEBUG(3,(" info: %s\n"
" dir: %s\n"
" shell: %s\n",
pw->pw_gecos, pw->pw_dir, pw->pw_shell));
sec_login_get_expiration(my_dce_sec_context, &expire_time, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
/* Go back to root, JRA. */
setuid(0);
setgid(0);
DEBUG(0,("DCE can't get expiration. %s\n", dce_errstr));
return(False);
}
setuid(0);
setgid(0);
DEBUG(0,("DCE context expires: %s",asctime(localtime(&expire_time))));
dcelogin_atmost_once = 1;
return (True);
}
void dfs_unlogin(void)
{
error_status_t err;
int err2;
unsigned char dce_errstr[dce_c_error_string_len];
sec_login_purge_context(&my_dce_sec_context, &err);
if (err != error_status_ok) {
dce_error_inq_text(err, dce_errstr, &err2);
DEBUG(0,("DCE purge login context failed for server instance %d: %s\n",
getpid(), dce_errstr));
}
}
#endif
#ifdef KRB5_AUTH
/*******************************************************************
check on Kerberos authentication
********************************************************************/
static BOOL krb5_auth(char *user,char *password)
{
krb5_data tgtname = {
0,
KRB5_TGS_NAME_SIZE,
KRB5_TGS_NAME
};
krb5_context kcontext;
krb5_principal kprinc;
krb5_principal server;
krb5_creds kcreds;
int options = 0;
krb5_address **addrs = (krb5_address **)0;
krb5_preauthtype *preauth = NULL;
krb5_keytab keytab = NULL;
krb5_timestamp now;
krb5_ccache ccache = NULL;
int retval;
char *name;
if (retval=krb5_init_context(&kcontext)) {
return(False);
}
if (retval = krb5_timeofday(kcontext, &now)) {
return(False);
}
if (retval = krb5_cc_default(kcontext, &ccache)) {
return(False);
}
if (retval = krb5_parse_name(kcontext, user, &kprinc)) {
return(False);
}
ZERO_STRUCT(kcreds);
kcreds.client = kprinc;
if ((retval = krb5_build_principal_ext(kcontext, &server,
krb5_princ_realm(kcontext, kprinc)->length,
krb5_princ_realm(kcontext, kprinc)->data,
tgtname.length,
tgtname.data,
krb5_princ_realm(kcontext, kprinc)->length,
krb5_princ_realm(kcontext, kprinc)->data,
0))) {
return(False);
}
kcreds.server = server;
retval = krb5_get_in_tkt_with_password(kcontext,
options,
addrs,
NULL,
preauth,
password,
0,
&kcreds,
0);
if (retval) {
return(False);
}
return(True);
}
#endif /* KRB5_AUTH */
#ifdef KRB4_AUTH
#include <krb.h>
/*******************************************************************
check on Kerberos authentication
********************************************************************/
static BOOL krb4_auth(char *user,char *password)
{
char realm[REALM_SZ];
char tkfile[MAXPATHLEN];
if (krb_get_lrealm(realm, 1) != KSUCCESS) {
(void) safe_strcpy(realm, KRB_REALM, sizeof (realm) - 1);
}
(void) slprintf(tkfile, sizeof(tkfile) - 1, "/tmp/samba_tkt_%d",
(int)getpid());
krb_set_tkt_string(tkfile);
if (krb_verify_user(user, "", realm,
password, 0,
"rmcd") == KSUCCESS) {
unlink(tkfile);
return 1;
}
unlink(tkfile);
return 0;
}
#endif /* KRB4_AUTH */
#ifdef LINUX_BIGCRYPT
/****************************************************************************
an enhanced crypt for Linux to handle password longer than 8 characters
****************************************************************************/
static int linux_bigcrypt(char *password,char *salt1, char *crypted)
{
#define LINUX_PASSWORD_SEG_CHARS 8
char salt[3];
int i;
StrnCpy(salt,salt1,2);
crypted +=2;
for ( i=strlen(password); i > 0; i -= LINUX_PASSWORD_SEG_CHARS) {
char * p = crypt(password,salt) + 2;
if (strncmp(p, crypted, LINUX_PASSWORD_SEG_CHARS) != 0)
return(0);
password += LINUX_PASSWORD_SEG_CHARS;
crypted += strlen(p);
}
return(1);
}
#endif
#ifdef OSF1_ENH_SEC
/****************************************************************************
an enhanced crypt for OSF1
****************************************************************************/
static char *osf1_bigcrypt(char *password,char *salt1)
{
static char result[AUTH_MAX_PASSWD_LENGTH] = "";
char *p1;
char *p2=password;
char salt[3];
int i;
int parts = strlen(password) / AUTH_CLEARTEXT_SEG_CHARS;
if (strlen(password)%AUTH_CLEARTEXT_SEG_CHARS) {
parts++;
}
StrnCpy(salt,salt1,2);
StrnCpy(result,salt1,2);
for (i=0; i<parts;i++) {
p1 = crypt(p2,salt);
strncat(result,p1+2,AUTH_MAX_PASSWD_LENGTH-strlen(p1+2)-1);
StrnCpy(salt,&result[2+i*AUTH_CIPHERTEXT_SEG_CHARS],2);
p2 += AUTH_CLEARTEXT_SEG_CHARS;
}
return(result);
}
#endif
/****************************************************************************
apply a function to upper/lower case combinations
of a string and return true if one of them returns true.
try all combinations with N uppercase letters.
offset is the first char to try and change (start with 0)
it assumes the string starts lowercased
****************************************************************************/
static BOOL string_combinations2(char *s,int offset,BOOL (*fn)(char *),int N)
{
int len = strlen(s);
int i;
#ifdef PASSWORD_LENGTH
len = MIN(len,PASSWORD_LENGTH);
#endif
if (N <= 0 || offset >= len) {
return(fn(s));
}
for (i=offset;i<(len-(N-1));i++) {
char c = s[i];
if (!islower(c)) continue;
s[i] = toupper(c);
if (string_combinations2(s,i+1,fn,N-1))
return(True);
s[i] = c;
}
return(False);
}
/****************************************************************************
apply a function to upper/lower case combinations
of a string and return true if one of them returns true.
try all combinations with up to N uppercase letters.
offset is the first char to try and change (start with 0)
it assumes the string starts lowercased
****************************************************************************/
static BOOL string_combinations(char *s,BOOL (*fn)(char *),int N)
{
int n;
for (n=1;n<=N;n++)
if (string_combinations2(s,0,fn,n)) return(True);
return(False);
}
/****************************************************************************
core of password checking routine
****************************************************************************/
static BOOL password_check(char *password)
{
#ifdef HAVE_PAM
/* This falls through if the password check fails
- if HAVE_CRYPT is not defined this causes an error msg
saying Warning - no crypt available
- if HAVE_CRYPT is defined this is a potential security hole
as it may authenticate via the crypt call when PAM
settings say it should fail.
if (pam_auth(user,password)) return(True);
Hence we make a direct return to avoid a second chance!!!
*/
return (pam_auth(this_user,password));
#endif
#ifdef WITH_AFS
if (afs_auth(this_user,password)) return(True);
#endif
#ifdef WITH_DFS
if (dfs_auth(this_user,password)) return(True);
#endif
#ifdef KRB5_AUTH
if (krb5_auth(this_user,password)) return(True);
#endif
#ifdef KRB4_AUTH
if (krb4_auth(this_user,password)) return(True);
#endif
#ifdef OSF1_ENH_SEC
{
BOOL ret = (strcmp(osf1_bigcrypt(password,this_salt),this_crypted) == 0);
if(!ret) {
DEBUG(2,("OSF1_ENH_SEC failed. Trying normal crypt.\n"));
ret = (strcmp((char *)crypt(password,this_salt),this_crypted) == 0);
}
return ret;
}
#endif
#ifdef ULTRIX_AUTH
return (strcmp((char *)crypt16(password, this_salt ),this_crypted) == 0);
#endif
#ifdef LINUX_BIGCRYPT
return(linux_bigcrypt(password,this_salt,this_crypted));
#endif
#ifdef HAVE_BIGCRYPT
return(strcmp(bigcrypt(password,this_salt),this_crypted) == 0);
#endif
#ifndef HAVE_CRYPT
DEBUG(1,("Warning - no crypt available\n"));
return(False);
#else
return(strcmp((char *)crypt(password,this_salt),this_crypted) == 0);
#endif
}
/****************************************************************************
check if a username/password is OK
the function pointer fn() points to a function to call when a successful
match is found and is used to update the encrypted password file
return True on correct match, False otherwise
****************************************************************************/
BOOL pass_check(char *user,char *password, int pwlen, struct passwd *pwd,
BOOL (*fn)(char *, char *))
{
pstring pass2;
int level = lp_passwordlevel();
const struct passwd *pass;
if (password) password[pwlen] = 0;
#if DEBUG_PASSWORD
DEBUG(100,("checking user=[%s] pass=",user));
dump_data(100, password, strlen(password));
#endif
if (!password) {
return(False);
}
if (((!*password) || (!pwlen)) && !lp_null_passwords()) {
return(False);
}
if (pwd && !user) {
pass = (struct passwd *) pwd;
user = pass->pw_name;
} else {
pass = Get_Pwnam(user,True);
}
DEBUG(4,("Checking password for user %s (l=%d)\n",user,pwlen));
if (!pass) {
DEBUG(3,("Couldn't find user %s\n",user));
return(False);
}
/* extract relevant info */
fstrcpy(this_user,pass->pw_name);
fstrcpy(this_salt,pass->pw_passwd);
/* crypt on some platforms (HPUX in particular)
won't work with more than 2 salt characters. */
this_salt[2] = 0;
fstrcpy(this_crypted,pass->pw_passwd);
if (!*this_crypted) {
if (!lp_null_passwords()) {
DEBUG(2,("Disallowing %s with null password\n",
this_user));
return(False);
}
if (!*password) {
DEBUG(3,("Allowing access to %s with null password\n",
this_user));
return(True);
}
}
/* try it as it came to us */
if (password_check(password)) {
if (fn) fn(user,password);
return(True);
}
/* if the password was given to us with mixed case then we don't
need to proceed as we know it hasn't been case modified by the
client */
if (strhasupper(password) && strhaslower(password)) {
return(False);
}
/* make a copy of it */
StrnCpy(pass2,password,sizeof(pstring)-1);
/* try all lowercase */
strlower(password);
if (password_check(password)) {
if (fn) fn(user,password);
return(True);
}
/* give up? */
if (level < 1) {
/* restore it */
fstrcpy(password,pass2);
return(False);
}
/* last chance - all combinations of up to level chars upper! */
strlower(password);
if (string_combinations(password,password_check,level)) {
if (fn) fn(user,password);
return(True);
}
/* restore it */
fstrcpy(password,pass2);
return(False);
}