// 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 <pwd.h>
#include <grp.h>
#include <utmp.h>
#include <time.h>
#include <fcntl.h>
static struct account** _accts = NULL;
static unsigned _naccts = 0;
static unsigned _selected_account = 0;
static gid_t _ttygroup = 0;
static void CleanupAccounts (void)
{
if (!_accts)
return;
for (unsigned i = 0; i < _naccts; ++i) {
xfree (_accts[i]->name);
xfree (_accts[i]->dir);
xfree (_accts[i]->shell);
xfree (_accts[i]);
}
xfreenull (_accts);
}
static bool CanLogin (const struct passwd* pw)
{
if (!pw->pw_shell || strstr (pw->pw_shell, "nologin"))
return false;
setusershell();
const char* ushell = getusershell();
if (!ushell)
return true; // /etc/shells missing - allow all shells
do {
if (!strcmp (pw->pw_shell, ushell))
return true;
} while ((ushell = getusershell()));
return false;
}
acclist_t ReadAccounts (void)
{
if (_accts)
return (acclist_t) _accts;
_ttygroup = getgid();
struct group* ttygr = getgrnam("tty");
if (ttygr) // If no tty group, use user's primary group
_ttygroup = ttygr->gr_gid;
endgrent();
unsigned nac = 0;
setpwent();
for (struct passwd* pw; (pw = getpwent());)
nac += CanLogin (pw);
_accts = xmalloc ((nac+1)*sizeof(struct account*));
atexit (CleanupAccounts);
unsigned iac = 0;
setpwent();
for (struct passwd* pw; iac < nac && (pw = getpwent());) {
if (!CanLogin (pw))
continue;
_accts[iac] = (struct account*) xmalloc (sizeof(struct account));
_accts[iac]->uid = pw->pw_uid;
_accts[iac]->gid = pw->pw_gid;
_accts[iac]->name = strdup (pw->pw_name);
_accts[iac]->dir = strdup (pw->pw_dir);
_accts[iac]->shell = strdup (pw->pw_shell);
++iac;
}
_naccts = iac;
endpwent();
endusershell();
// For each valid user, get the last login time from lastlog
int fd = open (_PATH_LASTLOG, O_RDONLY);
if (fd >= 0) {
struct stat st;
if (fstat (fd, &st) == 0) {
const unsigned maxuid = st.st_size / sizeof(struct lastlog);
for (unsigned i = 0; i < _naccts; ++i)
if (_accts[i]->uid < maxuid)
pread (fd, &_accts[i]->ltime, sizeof(_accts[i]->ltime), _accts[i]->uid * sizeof(struct lastlog));
}
close (fd);
}
// Reset the selected user index to the one logged in last
time_t maxtime = 0;
unsigned lli = 0;
for (unsigned i = 0; i < _naccts; ++i) {
if (_accts[i]->ltime >= maxtime) {
maxtime = _accts[i]->ltime;
lli = i;
}
}
_selected_account = lli;
return (acclist_t) _accts;
}
unsigned NAccounts (void)
{
return _naccts;
}
unsigned SelectedAccount (void)
{
return _selected_account;
}
void SetSelectedAccount (unsigned i)
{
if (i < _naccts)
_selected_account = i;
}
const struct account* SetSelectedAccountByName (const char* name)
{
if (!name)
return NULL;
acclist_t al = ReadAccounts();
for (unsigned i = 0; i < _naccts; ++i) {
if (0 != strcmp (name, al[i]->name))
continue;
_selected_account = i;
return al[i];
}
return NULL;
}
const struct account* SelectedAccountInfo (void)
{
acclist_t al = ReadAccounts();
return _selected_account < _naccts ? al[_selected_account] : NULL;
}
gid_t GetTTYGroup (void)
{
return (!_ttygroup && _selected_account < _naccts)
? _accts[_selected_account]->gid : _ttygroup;
}
void WriteLastlog (void)
{
if (_selected_account >= _naccts)
return;
uid_t luid = _accts[_selected_account]->uid;
int fd = open (_PATH_LASTLOG, O_WRONLY| O_CREAT, 644);
if (fd < 0)
return;
struct lastlog ll;
memset (&ll, 0, sizeof(ll));
const char* ttyname = strrchr (_ttypath, '/');
if (!ttyname++)
ttyname = _ttypath;
strncpy (ll.ll_line, ttyname, sizeof(ll.ll_line)-1);
gethostname (ll.ll_host, sizeof(ll.ll_host)-1);
ll.ll_time = time (NULL);
pwrite (fd, &ll, sizeof(ll), luid*sizeof(ll));
close (fd);
}
void WriteUtmp (const struct account* acct, pid_t pid, short uttype, int exitcode)
{
struct utmp ut;
memset (&ut, 0, sizeof(ut));
ut.ut_type = uttype;
ut.ut_pid = pid;
ut.ut_exit.e_exit = exitcode;
const char* ttydev = strrchr(_ttypath, '/');
if (!ttydev++)
ttydev = _ttypath;
strncpy (ut.ut_line, ttydev, sizeof(ut.ut_line)-1);
strncpy (ut.ut_id, ttydev, sizeof(ut.ut_id)); // ut_id is not a 0-terminated string
strncpy (ut.ut_user, acct ? acct->name : "LOGIN", sizeof(ut.ut_user)-1);
struct timeval tv;
gettimeofday (&tv, NULL);
ut.ut_tv.tv_sec = tv.tv_sec;
ut.ut_tv.tv_usec = tv.tv_usec;
setutent();
pututline (&ut);
endutent();
updwtmp (_PATH_WTMP, &ut);
}