/* Returns 0 if the user is successfully authenticated, and sets the appropriate group name. */ static int plain_auth_pass(void *ctx, const char *pass, unsigned pass_len) { struct plain_ctx_st *pctx = ctx; if (pctx->failed || (pctx->cpass[0] != 0 && strcmp(crypt(pass, pctx->cpass), pctx->cpass) != 0)) { if (pctx->retries++ < MAX_PASSWORD_TRIES-1) { pctx->pass_msg = pass_msg_failed; return ERR_AUTH_CONTINUE; } else { syslog(LOG_AUTH, "plain-auth: error authenticating user '%s'", pctx->username); return ERR_AUTH_FAIL; } } if (pctx->cpass[0] == 0 && otp_file == NULL) { syslog(LOG_AUTH, "plain-auth: user '%s' has empty password and no OTP file configured", pctx->username); return ERR_AUTH_FAIL; } #ifdef HAVE_LIBOATH if (otp_file != NULL) { int ret; time_t last; if (pctx->cpass[0] != 0) { /* we just checked the password */ pctx->cpass[0] = 0; pctx->pass_msg = pass_msg_otp; return ERR_AUTH_CONTINUE; } /* no primary password -> check OTP */ ret = oath_authenticate_usersfile(otp_file, pctx->username, pass, HOTP_WINDOW, NULL, &last); if (ret != OATH_OK) { syslog(LOG_AUTH, "plain-auth: OTP auth failed for '%s': %s", pctx->username, oath_strerror(ret)); return ERR_AUTH_FAIL; } } #endif if (pctx->failed) return ERR_AUTH_FAIL; return 0; }
int main (void) { oath_rc rc; time_t last_otp; struct stat ufstat1; struct stat ufstat2; if (!oath_check_version (OATH_VERSION)) { printf ("oath_check_version (%s) failed [%s]\n", OATH_VERSION, oath_check_version (NULL)); return 1; } rc = oath_init (); if (rc != OATH_OK) { printf ("oath_init: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } rc = oath_authenticate_usersfile ("no-such-file", "joe", "755224", 0, "1234", &last_otp); if (rc != OATH_NO_SUCH_FILE) { printf ("oath_authenticate_usersfile[1]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Record the current usersfile inode */ stat (CREDS, &ufstat1); rc = oath_authenticate_usersfile (CREDS, "joe", "755224", 0, "1234", &last_otp); if (rc != OATH_BAD_PASSWORD) { printf ("oath_authenticate_usersfile[2]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Check that we do not update usersfile on not OATH_OK */ stat (CREDS, &ufstat2); if (ufstat1.st_ino != ufstat2.st_ino) { printf ("oath_authenticate_usersfile[26]: usersfile %s changed " "on OATH_BAD_PASSWORD\n", CREDS); return 1; } rc = oath_authenticate_usersfile (CREDS, "bob", "755224", 0, "1234", &last_otp); if (rc != OATH_BAD_PASSWORD) { printf ("oath_authenticate_usersfile[3]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } rc = oath_authenticate_usersfile (CREDS, "silver", "670691", 0, "4711", &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[4]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } stat (CREDS, &ufstat2); if (ufstat1.st_ino == ufstat2.st_ino) { printf ("oath_authenticate_usersfile[27]: usersfile %s did not " "change on OATH_OK\n", CREDS); return 1; } rc = oath_authenticate_usersfile (CREDS, "silver", "599872", 1, "4711", &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[5]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } rc = oath_authenticate_usersfile (CREDS, "silver", "072768", 1, "4711", &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[6]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } stat (CREDS, &ufstat1); rc = oath_authenticate_usersfile (CREDS, "foo", "755224", 0, "8989", &last_otp); if (rc != OATH_REPLAYED_OTP) { printf ("oath_authenticate_usersfile[7]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } if (last_otp != 1260206742) { printf ("oath_authenticate_usersfile timestamp %ld != 1260203142\n", last_otp); return 1; } stat (CREDS, &ufstat2); if (ufstat1.st_ino != ufstat2.st_ino) { printf ("oath_authenticate_usersfile[28]: usersfile %s changed " "on OATH_REPLAYED_OTP\n", CREDS); return 1; } rc = oath_authenticate_usersfile (CREDS, "rms", "755224", 0, "4321", &last_otp); if (rc != OATH_BAD_PASSWORD) { printf ("oath_authenticate_usersfile[8]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } rc = oath_authenticate_usersfile (CREDS, "rms", "436521", 10, "6767", &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[9]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Run 'TZ=UTC oathtool --totp --now=2006-12-07 00 -w10' to generate: 963013 068866 734019 038980 630208 533058 042289 046988 047407 892423 619507 */ /* Test completely invalid OTP */ rc = oath_authenticate_usersfile (CREDS, "eve", "386397", 0, "4711", &last_otp); if (rc != OATH_BAD_PASSWORD) { printf ("oath_authenticate_usersfile[10]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Test the next OTP but search window = 0. */ rc = oath_authenticate_usersfile (CREDS, "eve", "068866", 0, NULL, &last_otp); if (rc != OATH_INVALID_OTP) { printf ("oath_authenticate_usersfile[11]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Test the next OTP with search window = 1. */ rc = oath_authenticate_usersfile (CREDS, "eve", "068866", 1, NULL, &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[12]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Test to replay last OTP. */ rc = oath_authenticate_usersfile (CREDS, "eve", "068866", 1, NULL, &last_otp); if (rc != OATH_REPLAYED_OTP) { printf ("oath_authenticate_usersfile[13]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Test to replay previous OTP. */ rc = oath_authenticate_usersfile (CREDS, "eve", "963013", 1, NULL, &last_otp); if (rc != OATH_REPLAYED_OTP) { printf ("oath_authenticate_usersfile[14]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try an OTP in the future but outside search window. */ rc = oath_authenticate_usersfile (CREDS, "eve", "892423", 1, NULL, &last_otp); if (rc != OATH_INVALID_OTP) { printf ("oath_authenticate_usersfile[15]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try OTP in the future with good search window. */ rc = oath_authenticate_usersfile (CREDS, "eve", "892423", 10, NULL, &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[16]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Now try a rather old OTP within search window. */ rc = oath_authenticate_usersfile (CREDS, "eve", "630208", 10, NULL, &last_otp); if (rc != OATH_REPLAYED_OTP) { printf ("oath_authenticate_usersfile[17]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try OTP that matches user's second line. */ rc = oath_authenticate_usersfile (CREDS, "twouser", "874680", 10, NULL, &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[18]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try OTP that matches user's third and final line. */ rc = oath_authenticate_usersfile (CREDS, "threeuser", "255509", 10, NULL, &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[19]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try OTP that matches user's third and next-to-last line. */ rc = oath_authenticate_usersfile (CREDS, "fouruser", "663447", 10, NULL, &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[19]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try incorrect OTP for user with five lines. */ rc = oath_authenticate_usersfile (CREDS, "fiveuser", "812658", 10, NULL, &last_otp); if (rc != OATH_INVALID_OTP) { printf ("oath_authenticate_usersfile[20]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try OTP that matches user's second line. */ rc = oath_authenticate_usersfile (CREDS, "fiveuser", "123001", 10, NULL, &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[21]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try OTP that matches user's fourth line. */ rc = oath_authenticate_usersfile (CREDS, "fiveuser", "893841", 10, NULL, &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[22]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try another OTP that matches user's second line. */ rc = oath_authenticate_usersfile (CREDS, "fiveuser", "746888", 10, NULL, &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[23]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try another OTP that matches user's fifth line. */ rc = oath_authenticate_usersfile (CREDS, "fiveuser", "730790", 10, NULL, &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[24]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Try too old OTP for user's second line. */ rc = oath_authenticate_usersfile (CREDS, "fiveuser", "692901", 10, NULL, &last_otp); if (rc != OATH_INVALID_OTP) { printf ("oath_authenticate_usersfile[25]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Test password field of + */ rc = oath_authenticate_usersfile (CREDS, "plus", "328482", 1, "4711", &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[26]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } rc = oath_authenticate_usersfile (CREDS, "plus", "812658", 1, "4712", &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[27]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Test different tokens with different passwords for one user */ rc = oath_authenticate_usersfile (CREDS, "password", "898463", 5, NULL, &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[28]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } rc = oath_authenticate_usersfile (CREDS, "password", "989803", 5, "test", &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[29]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } rc = oath_authenticate_usersfile (CREDS, "password", "427517", 5, "darn", &last_otp); if (rc != OATH_OK) { printf ("oath_authenticate_usersfile[30]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Valid OTP for first token but incorrect password. */ rc = oath_authenticate_usersfile (CREDS, "password", "917625", 5, "nope", &last_otp); if (rc != OATH_BAD_PASSWORD) { printf ("oath_authenticate_usersfile[31]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Valid OTP for second token but incorrect password. */ rc = oath_authenticate_usersfile (CREDS, "password", "459145", 5, "nope", &last_otp); if (rc != OATH_BAD_PASSWORD) { printf ("oath_authenticate_usersfile[32]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Valid OTP for first token but with password for second user. */ rc = oath_authenticate_usersfile (CREDS, "password", "917625", 5, "test", &last_otp); if (rc != OATH_BAD_PASSWORD) { printf ("oath_authenticate_usersfile[33]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Valid OTP for second token but with password for first user. */ rc = oath_authenticate_usersfile (CREDS, "password", "459145", 5, "", &last_otp); if (rc != OATH_BAD_PASSWORD) { printf ("oath_authenticate_usersfile[34]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } /* Valid OTP for third token but with password for second user. */ rc = oath_authenticate_usersfile (CREDS, "password", "633070", 9, "test", &last_otp); if (rc != OATH_BAD_PASSWORD) { printf ("oath_authenticate_usersfile[35]: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } rc = oath_done (); if (rc != OATH_OK) { printf ("oath_done: %s (%d)\n", oath_strerror_name (rc), rc); return 1; } return 0; }
PAM_EXTERN int pam_sm_authenticate (pam_handle_t * pamh, int flags, int argc, const char **argv) { int retval, rc; const char *user = NULL; const char *password = NULL; char otp[MAX_OTP_LEN + 1]; int password_len = 0; struct pam_conv *conv; struct pam_message *pmsg[1], msg[1]; struct pam_response *resp; int nargs = 1; struct cfg cfg; char *query_prompt = NULL; char *onlypasswd = strdup (""); /* empty passwords never match */ parse_cfg (flags, argc, argv, &cfg); retval = pam_get_user (pamh, &user, NULL); if (retval != PAM_SUCCESS) { DBG (("get user returned error: %s", pam_strerror (pamh, retval))); goto done; } DBG (("get user returned: %s", user)); if (cfg.try_first_pass || cfg.use_first_pass) { retval = pam_get_item (pamh, PAM_AUTHTOK, (const void **) &password); if (retval != PAM_SUCCESS) { DBG (("get password returned error: %s", pam_strerror (pamh, retval))); goto done; } DBG (("get password returned: %s", password)); } if (cfg.use_first_pass && password == NULL) { DBG (("use_first_pass set and no password, giving up")); retval = PAM_AUTH_ERR; goto done; } rc = oath_init (); if (rc != OATH_OK) { DBG (("oath_init() failed (%d)", rc)); retval = PAM_AUTHINFO_UNAVAIL; goto done; } if (password == NULL) { retval = pam_get_item (pamh, PAM_CONV, (const void **) &conv); if (retval != PAM_SUCCESS) { DBG (("get conv returned error: %s", pam_strerror (pamh, retval))); goto done; } pmsg[0] = &msg[0]; { const char *query_template = "One-time password (OATH) for `%s': "; size_t len = strlen (query_template) + strlen (user); size_t wrote; query_prompt = malloc (len); if (!query_prompt) { retval = PAM_BUF_ERR; goto done; } wrote = snprintf (query_prompt, len, query_template, user); if (wrote < 0 || wrote >= len) { retval = PAM_BUF_ERR; goto done; } msg[0].msg = query_prompt; } msg[0].msg_style = PAM_PROMPT_ECHO_OFF; resp = NULL; retval = conv->conv (nargs, (const struct pam_message **) pmsg, &resp, conv->appdata_ptr); free (query_prompt); query_prompt = NULL; if (retval != PAM_SUCCESS) { DBG (("conv returned error: %s", pam_strerror (pamh, retval))); goto done; } DBG (("conv returned: %s", resp->resp)); password = resp->resp; } if (password) password_len = strlen (password); else { DBG (("Could not read password")); retval = PAM_AUTH_ERR; goto done; } if (password_len < MIN_OTP_LEN) { DBG (("OTP too short: %s", password)); retval = PAM_AUTH_ERR; goto done; } else if (cfg.digits != 0 && password_len < cfg.digits) { DBG (("OTP shorter than digits=%d: %s", cfg.digits, password)); retval = PAM_AUTH_ERR; goto done; } else if (cfg.digits == 0 && password_len > MAX_OTP_LEN) { DBG (("OTP too long (and no digits=): %s", password)); retval = PAM_AUTH_ERR; goto done; } else if (cfg.digits != 0 && password_len > cfg.digits) { free (onlypasswd); onlypasswd = strdup (password); /* user entered their system password followed by generated OTP? */ onlypasswd[password_len - cfg.digits] = '\0'; DBG (("Password: %s ", onlypasswd)); memcpy (otp, password + password_len - cfg.digits, cfg.digits); otp[cfg.digits] = '\0'; retval = pam_set_item (pamh, PAM_AUTHTOK, onlypasswd); if (retval != PAM_SUCCESS) { DBG (("set_item returned error: %s", pam_strerror (pamh, retval))); goto done; } } else { strcpy (otp, password); password = NULL; } DBG (("OTP: %s", otp ? otp : "(null)")); { time_t last_otp; rc = oath_authenticate_usersfile (cfg.usersfile, user, otp, cfg.window, onlypasswd, &last_otp); DBG (("authenticate rc %d (%s: %s) last otp %s", rc, oath_strerror_name (rc) ? oath_strerror_name (rc) : "UNKNOWN", oath_strerror (rc), ctime (&last_otp))); } if (rc != OATH_OK) { DBG (("One-time password not authorized to login as user '%s'", user)); retval = PAM_AUTH_ERR; goto done; } retval = PAM_SUCCESS; done: oath_done (); free (query_prompt); free (onlypasswd); if (cfg.alwaysok && retval != PAM_SUCCESS) { DBG (("alwaysok needed (otherwise return with %d)", retval)); retval = PAM_SUCCESS; } DBG (("done. [%s]", pam_strerror (pamh, retval))); return retval; }