// This file is part of the loginx project
//
// Copyright (c) 2013 by Mike Sharov <msharov@users.sourceforge.net>
// This file is free software, distributed under the ISC license.
#include "defs.h"
#include <pam_appl.h>
#include <pam_ext.h>
#include <grp.h>
#include <pwd.h>
//----------------------------------------------------------------------
static int xconv (int num_msg, const struct pam_message** msgm, struct pam_response** response, void* appdata_ptr);
//----------------------------------------------------------------------
static pam_handle_t* _pamh = NULL;
static char _password [MAX_PW_LEN] = {};
//----------------------------------------------------------------------
static void PamSetEnvironment (void)
{
const char* tty = ttyname (STDIN_FILENO);
if (tty)
pam_set_item (_pamh, PAM_TTY, tty);
}
static bool PamLogError (const char* fn, int ec)
{
char errbuf [128];
snprintf (errbuf, sizeof(errbuf), "%s: %s", fn, pam_strerror(_pamh,ec));
pam_syslog (_pamh, LOG_ERR, "%s", errbuf);
AddMessage (errbuf, true);
return false;
}
void PamOpen (void)
{
static const struct pam_conv conv = { xconv, NULL };
int r = pam_start (LOGINX_NAME, NULL, &conv, &_pamh);
if (r != PAM_SUCCESS) {
pam_syslog (_pamh, LOG_ERR, "pam_start: %s", pam_strerror(_pamh,r));
exit (EXIT_FAILURE);
}
PamSetEnvironment();
atexit (PamClose);
}
void PamClose (void)
{
if (!_pamh)
return;
PamLogout();
pam_end (_pamh, PAM_SUCCESS);
_pamh = NULL;
}
bool PamLogin (void)
{
// Ensure PAM asks for the user, because that's the
// only time the conversation will ask for a password.
pam_set_item (_pamh, PAM_USER, NULL);
// Run authentication
int r;
do
r = pam_authenticate (_pamh, 0);
while (r == PAM_INCOMPLETE);
memset (_password, 0, sizeof(_password));
if (r != PAM_SUCCESS)
return PamLogError ("pam_authenticate", r);
r = pam_acct_mgmt (_pamh, 0);
// Requiring password change at login time is a very bad idea.
// The user can not be expected to come up with a good password
// on short notice. Also, the root filesystem could be mounted r/o.
if (r == PAM_NEW_AUTHTOK_REQD)
r = PAM_SUCCESS; // so, just ignore the demand
if (r != PAM_SUCCESS)
return PamLogError ("pam_acct_mgmt", r);
// Verify that a valid account was selected
const char* username = NULL;
r = pam_get_item (_pamh, PAM_USER, (const void**) &username);
if (r != PAM_SUCCESS || !username)
return PamLogError ("pam_get_item(PAM_USER)", r);
const struct account* acct = SetSelectedAccountByName (username);
if (!acct)
return PamLogError ("SetSelectedAccountByName", r);
// Initialize credentials
initgroups (acct->name, acct->gid);
r = pam_setcred(_pamh, PAM_ESTABLISH_CRED);
if (r != PAM_SUCCESS)
return PamLogError ("pam_setcred", r);
r = pam_open_session (_pamh, 0);
if (r != PAM_SUCCESS)
return PamLogError ("pam_open_session", r);
// Done with interactive login, remove the greeter
CursesCleanup();
// Give ownership of the tty
fchown (STDIN_FILENO, acct->uid, GetTTYGroup());
fchmod (STDIN_FILENO, 0620);
// Log the message
if (!acct->uid)
pam_syslog (_pamh, LOG_NOTICE, "ROOT LOGIN ON %s", _ttyname);
else
pam_syslog (_pamh, LOG_INFO, "LOGIN ON %s BY %s", _ttyname, acct->name);
return true;
}
void PamLogout (void)
{
memset (_password, 0, sizeof(_password));
// Retake ownership of the tty
fchown (STDIN_FILENO, getuid(), GetTTYGroup());
fchmod (STDIN_FILENO, 0620);
pam_close_session (_pamh, PAM_SILENT);
pam_setcred (_pamh, PAM_SILENT| PAM_DELETE_CRED);
}
const char* PamGetenv (const char* name)
{
return pam_getenv (_pamh, name);
}
static int xconv (int num_msg, const struct pam_message** msgm, struct pam_response** response, void* appdata_ptr __attribute__((unused)))
{
if (num_msg <= 0)
return PAM_CONV_ERR;
struct pam_response* reply = calloc (num_msg, sizeof(struct pam_response));
if (!reply)
return PAM_CONV_ERR;
int result = PAM_SUCCESS;
for (int i = 0; i < num_msg; ++i) {
const unsigned mss = msgm[i]->msg_style;
if (mss == PAM_PROMPT_ECHO_OFF) {
if (0 != strcmp (msgm[i]->msg, _(PAM_PASSWORD_PROMPT))) {
// If not the main password prompt, ask
reply[i].resp = calloc (MAX_PW_LEN, sizeof(char));
LoginBox (msgm[i]->msg, false, reply[i].resp, MAX_PW_LEN);
} else
reply[i].resp = strdup (_password);
} else if (mss == PAM_PROMPT_ECHO_ON) {
LoginBox (msgm[i]->msg, true, _password, sizeof(_password));
const struct account* acct = SelectedAccountInfo();
reply[i].resp = strdup (acct ? acct->name : "root");
} else if (mss == PAM_TEXT_INFO) {
AddMessage (msgm[i]->msg, false);
pam_syslog (_pamh, LOG_INFO, "%s", msgm[i]->msg);
} else if (mss == PAM_ERROR_MSG) {
AddMessage (msgm[i]->msg, true);
pam_syslog (_pamh, LOG_ERR, "%s", msgm[i]->msg);
} else { // Anything else fails login
// Delete any messages already processed
for (int j = 0; j < num_msg; ++j) {
if (reply[j].resp) {
memset (reply[j].resp, 0, strlen(reply[j].resp));
xfreenull (reply[j].resp);
}
}
xfreenull (reply);
pam_syslog (_pamh, LOG_ERR, _("erroneous conversation (%d)\n"), mss);
result = PAM_CONV_ERR;
break;
}
}
*response = reply;
return result;
}