/* This file is part of pam-modules. * Copyright (C) 2001, 2005, 2007-2008, 2010-2012, 2014-2015 Sergey * Poznyakoff * * 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 3 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, see . */ /* pam_fshadow */ #include #include #include #ifdef HAVE_SHADOW_H #include #endif #if defined(HAVE_CRYPT_H) # include #else extern char *crypt(const char *, const char *); #endif #define PAM_SM_AUTH #define PAM_SM_ACCOUNT #include #if !HAVE_FGETPWENT static struct passwd * fgetpwent(FILE *fp) { static char *buffer; static size_t buflen; static struct passwd pwbuf; size_t pos = 0; int c; size_t off[6]; int i = 0; while ((c = fgetc(fp)) != EOF) { if (pos == buflen) { char *nb; size_t ns; if (buflen == 0) ns = 128; else { ns = ns * 2; if (ns < buflen) { errno = ENOMEM; return NULL; } } nb = realloc(buffer, ns); if (!nb) return NULL; buffer = nb; buflen = ns; } if (c == '\n') { buffer[pos++] = 0; if (i != sizeof(off)/sizeof(off[0])) continue; break; } if (c == ':') { buffer[pos++] = 0; if (i < sizeof(off)/sizeof(off[0])) off[i++] = pos; } else buffer[pos++] = c; } if (pos == 0) return NULL; pwbuf.pw_name = buffer; pwbuf.pw_passwd = buffer + off[0]; pwbuf.pw_uid = strtoul(buffer + off[1], NULL, 10); pwbuf.pw_gid = strtoul(buffer + off[2], NULL, 10); pwbuf.pw_gecos = buffer + off[3]; pwbuf.pw_dir = buffer + off[4]; pwbuf.pw_shell = buffer + off[5]; return &pwbuf; } #endif #define CNTL_AUTHTOK 0x0010 #define CNTL_PASSWD 0x0020 #define CNTL_SHADOW 0x0040 #define CNTL_REGEX 0x0080 #define CNTL_REVERT_INDEX 0x0100 static regex_t rexp; static char *sysconfdir = SYSCONFDIR; static int cntl_flags = CNTL_PASSWD|CNTL_SHADOW; static long debug_level = 0; static const char *regex_str = NULL; static int regex_flags = REG_EXTENDED; static long username_index = 1; static long domain_index = 2; struct pam_opt pam_opt[] = { { PAM_OPTSTR(debug), pam_opt_long, &debug_level }, { PAM_OPTSTR(debug), pam_opt_const, &debug_level, { 1 } }, { PAM_OPTSTR(audit), pam_opt_const, &debug_level, { 100 } }, { PAM_OPTSTR(waitdebug), pam_opt_null, NULL, { 0 }, gray_wait_debug_fun }, { PAM_OPTSTR(use_authtok), pam_opt_bitmask, &cntl_flags, { CNTL_AUTHTOK } }, { PAM_OPTSTR(sysconfdir), pam_opt_string, &sysconfdir }, { PAM_OPTSTR(regex), pam_opt_string, ®ex_str }, { PAM_OPTSTR(extended), pam_opt_bitmask, ®ex_flags, { .value = REG_EXTENDED } }, { PAM_OPTSTR(basic), pam_opt_bitmask_rev, ®ex_flags, { .value = REG_EXTENDED } }, { PAM_OPTSTR(icase), pam_opt_bitmask, ®ex_flags, { .value = REG_ICASE } }, { PAM_OPTSTR(ignore-case), pam_opt_bitmask, ®ex_flags, { .value = REG_ICASE } }, { PAM_OPTSTR(case), pam_opt_bitmask_rev, ®ex_flags, { .value = REG_ICASE } }, { PAM_OPTSTR(passwd), pam_opt_bool, &cntl_flags, { .value = CNTL_PASSWD } }, { PAM_OPTSTR(shadow), pam_opt_bool, &cntl_flags, { .value = CNTL_SHADOW } }, { PAM_OPTSTR(revert-index), pam_opt_bool, &cntl_flags, { .value = CNTL_REVERT_INDEX } }, { PAM_OPTSTR(username-index), pam_opt_long, &username_index }, { PAM_OPTSTR(domain-index), pam_opt_long, &domain_index }, { NULL } }; static int _pam_parse(pam_handle_t *pamh, int argc, const char **argv) { int retval = PAM_SUCCESS; memset(&rexp, 0, sizeof(rexp)); regex_str = NULL; regex_flags = REG_EXTENDED; username_index = 1; domain_index = 2; gray_log_init(0, MODULE_NAME, LOG_AUTHPRIV); if (gray_parseopt(pam_opt, argc, argv)) return PAM_AUTHINFO_UNAVAIL; if ((cntl_flags & (CNTL_PASSWD|CNTL_SHADOW)) == 0) { _pam_log(LOG_CRIT, "either passwd or shadow must be true"); return PAM_AUTHINFO_UNAVAIL; } if (username_index <= 0) { _pam_log(LOG_CRIT, "username-index out of range"); return PAM_AUTHINFO_UNAVAIL; } if (domain_index <= 0) { _pam_log(LOG_CRIT, "domain-index out of range"); return PAM_AUTHINFO_UNAVAIL; } if (cntl_flags & CNTL_REVERT_INDEX) { long t = username_index; username_index = domain_index; domain_index = t; } if (regex_str) { int rc; if ((rc = regcomp(&rexp, regex_str, regex_flags))) { size_t s = regerror(rc, &rexp, NULL, 0); char *buf = malloc (s); if (buf) { regerror(rc, &rexp, buf, s); _pam_log(LOG_NOTICE, "cannot compile regex `%s': %s", regex_str, buf); free (buf); } else _pam_log(LOG_NOTICE, "cannot compile regex `%s'", regex_str); retval = PAM_AUTHINFO_UNAVAIL; } else if (!(username_index <= rexp.re_nsub && domain_index <= rexp.re_nsub)) { if (username_index > rexp.re_nsub) _pam_log(LOG_NOTICE, "not enough parenthesized groups" " to satisfy username-index"); if (domain_index > rexp.re_nsub) _pam_log(LOG_NOTICE, "not enough parenthesized groups" " to satisfy domain-index"); regfree(&rexp); retval = PAM_AUTHINFO_UNAVAIL; } else cntl_flags |= CNTL_REGEX; } return retval; } static int _pam_get_password(pam_handle_t *pamh, char **password, const char *prompt) { char *item, *token; int retval; struct pam_message msg[3], *pmsg[3]; struct pam_response *resp; int i, replies; DEBUG(90,("enter _pam_get_password")); if (cntl_flags & CNTL_AUTHTOK) { /* * get the password from the PAM item */ retval = pam_get_item(pamh, PAM_AUTHTOK, (const void **) &item); if (retval != PAM_SUCCESS) { /* very strange. */ _pam_log(LOG_ALERT, "can't retrieve password item: %s", pam_strerror(pamh, retval)); return retval; } else if (item != NULL) { *password = item; item = NULL; return PAM_SUCCESS; } else return PAM_AUTHTOK_RECOVER_ERR; } /* * ask user for the password */ /* prepare to converse */ i = 0; pmsg[i] = &msg[i]; msg[i].msg_style = PAM_PROMPT_ECHO_OFF; msg[i++].msg = (const void*)prompt; replies = 1; /* run conversation */ resp = NULL; token = NULL; retval = gray_converse(pamh, i, pmsg, &resp); if (resp != NULL) { if (retval == PAM_SUCCESS) { /* a good conversation */ token = XSTRDUP(resp[i - replies].resp); pam_set_item(pamh, PAM_AUTHTOK, token); DEBUG(100,("app returned [%s]", token)); PAM_DROP_REPLY(resp, 1); } else { _pam_log(LOG_ERR, "conversation error: %s", pam_strerror(pamh, retval)); } } else { retval = (retval == PAM_SUCCESS) ? PAM_AUTHTOK_RECOVER_ERR : retval; } if (retval == PAM_SUCCESS) { /* * keep password as data specific to this module. pam_end() * will arrange to clean it up. */ retval = pam_set_data(pamh, "password", (void *)token, gray_cleanup_string); if (retval != PAM_SUCCESS) { _pam_log(LOG_CRIT, "can't keep password: %s", pam_strerror(pamh, retval)); gray_pam_delete(token); } else { *password = token; token = NULL; /* break link to password */ } } else { _pam_log(LOG_ERR, "unable to obtain a password: %s", pam_strerror(pamh, retval)); } DEBUG(90,("exit _pam_get_password: %d", retval)); return retval; } char * mkfilename(const char *dir, const char *name) { int len = strlen(dir) + strlen(name); char *p = malloc(len+2); if (!p) { _pam_log(LOG_EMERG, "not enough memory"); abort (); } sprintf(p, "%s/%s", dir, name); return p; } int verify_user_acct(const char *confdir, const char *username, char **pwd) { char *filename = mkfilename(confdir, "passwd"); FILE *fp; int retval; DEBUG(10,("Looking up user `%s' in `%s'", username, filename)); *pwd = NULL; fp = fopen (filename, "r"); if (fp) { struct passwd *pw; while ((pw = fgetpwent(fp)) != NULL) { if (strcmp (pw->pw_name, username) == 0) break; } if (!pw) { _pam_log(LOG_ERR, "user %s not found in %s", username, filename); retval = PAM_USER_UNKNOWN; } else { if (pw->pw_passwd && strlen(pw->pw_passwd) > 1) *pwd = strdup(pw->pw_passwd); retval = PAM_SUCCESS; } } else { _pam_log(LOG_ERR, "can't open %s: %s", filename, strerror(errno)); retval = PAM_SERVICE_ERR; } free(filename); return retval; } int verify_user_pass(const char *confdir, const char *username, const char *password) { #if defined(HAVE_FGETSPENT) && defined(HAVE_STRUCT_SPWD) struct spwd *sp = NULL; time_t curdays; FILE *fp; int retval = PAM_AUTH_ERR; char *shadow = mkfilename(confdir, "shadow"); if (debug_level == 100) _pam_debug("Verifying user `%s' with password `%s' in `%s'", username, password, shadow); else if (debug_level >= 10) _pam_debug("Verifying user `%s' in `%s'", username, password, shadow); fp = fopen(shadow, "r"); if (!fp) { _pam_log(LOG_ERR, "can't open %s: %s", shadow, strerror(errno)); free(shadow); return PAM_SERVICE_ERR; } while ((sp = fgetspent(fp)) != NULL && strcmp(sp->sp_namp, username)) ; fclose(fp); if (!sp) { _pam_log(LOG_ERR, "entry for %s not found in %s", username, shadow); free(shadow); return PAM_USER_UNKNOWN; } /* We have the user's information, now let's check if his account has expired */ curdays = time(NULL) / (60 * 60 * 24); if (sp->sp_min != -1 && curdays < sp->sp_lstchg + sp->sp_min) retval = PAM_AUTHTOK_ERR; else if (sp->sp_max != -1 && sp->sp_inact != -1 && sp->sp_lstchg != 0 && curdays > sp->sp_lstchg + sp->sp_max + sp->sp_inact) /* Password is too old */ retval = PAM_ACCT_EXPIRED; #if defined(HAVE_STRUCT_SPWD_SP_EXPIRE) else if (sp->sp_expire != -1 && sp->sp_lstchg != 0 && curdays > sp->sp_expire) /* Account has expired */ retval = PAM_ACCT_EXPIRED; #endif else if (strcmp(sp->sp_pwdp, crypt(password, sp->sp_pwdp)) == 0) retval = PAM_SUCCESS; else retval = PAM_AUTH_ERR; free(shadow); return retval; #else return PAM_AUTH_ERR; #endif } static int copy_backref (pam_handle_t *pamh, const char *name, const char *buf, regmatch_t rmatch[3], int index, char **pstr) { char *str; size_t size; int rc; if (rmatch[index].rm_so == -1) size = 0; else size = rmatch[index].rm_eo - rmatch[index].rm_so; str = malloc (size + 1); if (!str) { _pam_log(LOG_CRIT, "not enough memory"); return PAM_SYSTEM_ERR; } rc = pam_set_data(pamh, name, (void *)str, gray_cleanup_string); if (rc != PAM_SUCCESS) { _pam_log(LOG_CRIT, "can't keep data [%s]: %s", name, pam_strerror(pamh, rc)); gray_pam_delete(str); } else { if (size != 0) memcpy(str, buf + rmatch[index].rm_so, size); str[size] = 0; *pstr = str; } return rc; } /* --- authentication management functions (only) --- */ static int translate(pam_handle_t *pamh, char const *input, char **ret_username, char **ret_confdir) { size_t nmatch = (domain_index > username_index ? domain_index : username_index) + 1; regmatch_t *rmatch; int rc; rmatch = calloc(nmatch, sizeof(rmatch[0])); if (!rmatch) { _pam_log(LOG_ERR, "out of memory"); return PAM_SERVICE_ERR; } if (regexec(&rexp, input, nmatch, rmatch, 0) == 0) { char *domain; if ((rc = copy_backref(pamh, "DOMAIN", input, rmatch, domain_index, &domain)) == PAM_SUCCESS && ((rc = copy_backref(pamh, "USERNAME", input, rmatch, username_index, ret_username)) == PAM_SUCCESS)) { *ret_confdir = mkfilename(sysconfdir, domain); pam_set_data(pamh, "CONFDIR", (void *)*ret_confdir, gray_cleanup_string); } } else { DEBUG(1,("user name `%s' does not match regular " "expression `%s'", input, regex_str)); rc = PAM_AUTH_ERR; } free(rmatch); return rc; } PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { const char *input_username; char *username; char *password; int retval = PAM_AUTH_ERR; int rc; char *confdir; char *pwstr = NULL; /* parse arguments */ if ((rc = _pam_parse(pamh, argc, argv)) != PAM_SUCCESS) return rc; confdir = sysconfdir; /* Get the username */ retval = pam_get_user(pamh, &input_username, NULL); if (retval != PAM_SUCCESS || !input_username) { DEBUG(1,("can not get the username")); if (cntl_flags & CNTL_REGEX) regfree(&rexp); return PAM_SERVICE_ERR; } if (cntl_flags & CNTL_REGEX) { retval = translate(pamh, input_username, &username, &confdir); regfree(&rexp); } else { retval = PAM_SUCCESS; username = (char *) input_username; confdir = sysconfdir; } if (retval != PAM_SUCCESS) return retval; /* Get the password */ if (_pam_get_password(pamh, &password, "Password:")) return PAM_SERVICE_ERR; if (cntl_flags & CNTL_PASSWD) retval = verify_user_acct(confdir, username, &pwstr); else retval = PAM_SUCCESS; if (retval == PAM_SUCCESS) { if (pwstr) { if (strcmp(pwstr, crypt(password, pwstr)) == 0) retval = PAM_SUCCESS; else retval = PAM_AUTH_ERR; free(pwstr); } else if (cntl_flags & CNTL_SHADOW) retval = verify_user_pass(confdir, username, password); } switch (retval) { case PAM_ACCT_EXPIRED: _pam_log(LOG_NOTICE, "user '%s': account expired", username); break; case PAM_SUCCESS: _pam_log(LOG_NOTICE, "user '%s' granted access", username); break; default: _pam_log(LOG_NOTICE, "user '%s' failed to authenticate", username); } return retval; } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_SUCCESS; } PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { return PAM_SUCCESS; } #ifdef PAM_STATIC /* static module data */ struct pam_module _pam_fshadow_modstruct = { "pam_fshadow", pam_sm_authenticate, pam_sm_setcred, NULL, NULL, NULL, NULL, }; #endif