/* 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(¤t_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); }