timeoutd/timeoutd.c
2021-01-05 19:58:12 +01:00

1179 lines
37 KiB
C

/*
"@(#) timeoutd.c 1.6 by Shane Alderton"
based on:
"@(#) autologout.c by David Dickson"
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.
Thanks to:
David Dickson for writing the original autologout.c
programme upon which this programme was based.
*/
/* #define DEBUG _DEBUG_ */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <utmp.h>
#include <pwd.h>
#include <grp.h>
#include <sys/syslog.h>
#include <time.h>
#include <sys/resource.h>
#include <ctype.h>
#include <fcntl.h>
#ifdef TIMEOUTDX11
#include <netdb.h>
#include <X11/Xlib.h>
#include <X11/extensions/scrnsaver.h>
#define TIMEOUTD_XSESSION_NONE 0
#define TIMEOUTD_XSESSION_LOCAL 1
#define TIMEOUTD_XSESSION_REMOTE 2
#endif
#define OPENLOG_FLAGS LOG_CONS|LOG_PID
#ifndef CONFIG
#define CONFIG "/etc/timeouts"
#endif
#define MAXLINES 512
#define max(a,b) ((a)>(b)?(a):(b))
#define ALLOWED 1
#define IDLEMAX 2
#define SESSMAX 3
#define DAYMAX 4
#define NOLOGIN 5
#define IDLEMSG 0
#define SESSMSG 1
#define DAYMSG 2
#define NOLOGINMSG 3
#define KWAIT 5 /* Time to wait after sending a kill signal */
char *limit_names[] = { "idle", "session", "daily", "nologin" };
char *daynames[] = {"SU", "MO", "TU", "WE", "TH", "FR", "SA", "WK", "AL", NULL};
char daynums[] = { 1 , 2 , 4 , 8 , 16 , 32 , 64 , 62 , 127, 0};
struct utmp *utmpp; /* pointer to utmp file entry */
void shut_down();
void read_config();
void reread_config();
void reapchild();
void free_wtmp();
void check_idle();
void read_wtmp();
void bailout();
char chk_timeout();
void logoff_msg();
void killit();
int chk_xsession(); /* seppy: is it a X-Session? */
void killit_xsession(); /* seppy: kill the X-Session */
void segfault(); /* seppy: catch segfault and log them */
int chk_xterm(); /* seppy: is it a xterm? */
#ifdef TIMEOUTDX11
Time get_xidle(); /* seppy: how long is user idle? (user,display) */
#endif
struct ut_list {
struct utmp elem;
struct ut_list *next;
};
struct ut_list *wtmplist = (struct ut_list *) NULL;
struct time_ent {
int days;
int starttime;
int endtime;
};
struct config_ent {
struct time_ent *times;
char *ttys;
char *users;
char *groups;
char login_allowed;
int idlemax;
int sessmax;
int daymax;
int warntime;
char *messages[4];
};
struct config_ent *config[MAXLINES + 1];
char dev[sizeof(utmpp->ut_line) + 1];
unsigned char limit_type;
int configline = 0;
int pending_reread = 0;
int allow_reread = 0;
time_t time_now;
struct tm now;
int now_hhmm;
int daytime = 0; /* Amount of time a user has been on in current day */
char path[255]; /*seppy */
FILE *proc_file; /*seppy */
char comm[16]; /*seppy; to save the command of a pid */
int main(argc, argv)
int argc;
char *argv[];
{
signal(SIGTERM, shut_down);
signal(SIGHUP, reread_config);
signal(SIGCHLD, reapchild);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
signal(SIGSEGV, segfault);
openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON);
/* The only valid invocations are "timeoutd" or "timeoutd user tty" */
if (argc != 1 && argc != 3) {
syslog(LOG_ERR, "Incorrect invocation of timeoutd (argc=%d) by UID %d.", argc, getuid());
exit(5);
}
/* read config file into memory */
read_config();
/* Change into the root filesystem to avoid "device busy" errors when the
* filesystem from which we were started is unmounted. /dev is convenient as
* ut_line fields are relative to it.
*/
if (chdir("/dev")) {
syslog(LOG_ERR, "Could not change working directory to /dev!");
exit(1);
}
/* Handle the "timeoutd user tty" invocation */
/* This is a bit of a shameless hack, but, well, it works. */
if (argc == 3) {
#ifdef DEBUG
syslog(LOG_DEBUG, "Running in user check mode. Checking user %s on %s.", argv[1],
argv[2]);
#endif
strncpy(dev, argv[2], sizeof(dev) - 1);
dev[sizeof(dev) - 1] = '\0';
time_now = time((time_t *) 0); /* get current time */
now = *(localtime(&time_now)); /* Break it into bits */
now_hhmm = now.tm_hour * 100 + now.tm_min;
allow_reread = 0;
read_wtmp(); /* Read in today's wtmp entries */
switch (chk_timeout(argv[1], dev, "", 0, 0)) {
case DAYMAX:
syslog(LOG_NOTICE,
"User %s on %s exceeded maximum daily limit (%d minutes). Login check failed.",
argv[1], argv[2], config[configline]->daymax);
logoff_msg(1);
exit(10);
case NOLOGIN:
syslog(LOG_NOTICE,
"User %s not allowed to login on %s at this time. Login check failed.", argv[1],
argv[2]);
logoff_msg(1);
exit(20);
case ALLOWED:
#ifdef DEBUG
syslog(LOG_DEBUG, "User %s on %s passed login check.", argv[1], argv[2]);
#endif
free_wtmp();
exit(0);
default:
syslog(LOG_ERR,
"Internal error checking user %s on %s - unexpected return from chk_timeout",
argv[1], argv[2]);
exit(30);
}
}
/* If running in daemon mode (no parameters) */
pid_t pid;
if ((pid = fork()) < 0) {
syslog(LOG_ERR, "Failed to execute fork number 1");
exit(1);
}
if (pid > 0)
exit(0);
struct rlimit r;
if (getrlimit(RLIMIT_NOFILE, &r) == -1) {
syslog(LOG_ERR, "Coudln't get file resource limit.");
exit(1);
}
for (int i = r.rlim_cur; i >= 0; --i) {
close(i);
}
if (setsid() < 0) {
syslog(LOG_ERR, "Failed to set new session ID at startup.");
exit(1);
}
if ((pid = fork()) < 0) {
syslog(LOG_ERR, "Failed to execute fork number 2");
exit(1);
}
if (pid > 0)
exit(0);
umask(0);
syslog(LOG_NOTICE, "Daemon started.");
/* the child processes all utmp file entries: */
while (1) {
/* Record time at which we woke up & started checking */
time_now = time((time_t *) 0); /* get current time */
now = *(localtime(&time_now)); /* Break it into bits */
now_hhmm = now.tm_hour * 100 + now.tm_min;
allow_reread = 0;
read_wtmp(); /* Read in today's wtmp entries */
setutent();
#ifdef DEBUG
syslog(LOG_DEBUG, "Time to check utmp for exceeded limits.");
#endif
while ((utmpp = getutent()) != (struct utmp *) NULL)
check_idle();
free_wtmp(); /* Free up memory used by today's wtmp entries */
allow_reread = 1;
if (pending_reread)
reread_config(SIGHUP);
#ifdef DEBUG
syslog(LOG_DEBUG, "Finished checking utmp... sleeping for 1 minute.");
#endif
sleep(60);
}
}
/* Read in today's wtmp entries */
void read_wtmp()
{
FILE *fp;
struct utmp ut;
struct ut_list *ut_list_p;
struct tm *tm;
#ifdef DEBUG
syslog(LOG_DEBUG, "Reading today's wtmp entries.");
#endif
if ((fp = fopen(WTMP_FILE, "r")) == NULL)
bailout("Could not open wtmp file!", 1);
#ifdef DEBUG
syslog(LOG_DEBUG, "Seek to end of wtmp");
#endif
/* Go to end of file minus one structure */
fseek(fp, -1L * sizeof(struct utmp), SEEK_END);
while (fread(&ut, sizeof(struct utmp), 1, fp) == 1) {
/* ut.ut_tv.tv_sec is not guaranteed to be time_t and localtime requires a time_t
argument, and will break otherwise */
time_t tmp_time = ut.ut_tv.tv_sec;
tm = localtime(&tmp_time);
if (tm->tm_year != now.tm_year || tm->tm_yday != now.tm_yday)
break;
if (ut.ut_type == USER_PROCESS || ut.ut_type == DEAD_PROCESS || ut.ut_type == UT_UNKNOWN || /* SA 19940703 */
ut.ut_type == LOGIN_PROCESS || ut.ut_type == BOOT_TIME)
{
if ((ut_list_p = (struct ut_list *)
malloc(sizeof(struct ut_list))) == NULL)
bailout("Out of memory in read_wtmp.", 1);
ut_list_p->elem = ut;
ut_list_p->next = wtmplist;
wtmplist = ut_list_p;
}
if (fseek(fp, -2 * sizeof(struct utmp), SEEK_CUR) < 0)
break;
}
fclose(fp);
#ifdef DEBUG
syslog(LOG_DEBUG, "Finished reading today's wtmp entries.");
#endif
}
/* Free up memory used by today's wtmp entries */
void free_wtmp()
{
struct ut_list *ut_list_p;
#ifdef DEBUG
syslog(LOG_DEBUG, "Freeing list of today's wtmp entries.");
#endif
while (wtmplist) {
#ifdef DEBUG_WTMP
struct tm *tm;
tm = localtime(&(wtmplist->elem.ut_time));
printf("%d:%d %s %s %s\n", tm->tm_hour, tm->tm_min, wtmplist->elem.ut_line,
wtmplist->elem.ut_user,
wtmplist->elem.ut_type ==
LOGIN_PROCESS ? "login" : wtmplist->elem.ut_type == BOOT_TIME ? "reboot" : "logoff");
#endif
ut_list_p = wtmplist;
wtmplist = wtmplist->next;
free(ut_list_p);
}
#ifdef DEBUG
syslog(LOG_DEBUG, "Finished freeing list of today's wtmp entries.");
#endif
}
void store_times(t, time_str)
struct time_ent **t;
char *time_str;
{
int i = 0;
int ar_size = 2;
char *p;
struct time_ent *te;
while (time_str[i])
if (time_str[i++] == ',')
ar_size++;
if ((*t = (struct time_ent *) malloc(ar_size * sizeof(struct time_ent))) == NULL)
bailout("Out of memory", 1);
te = *t;
p = strtok(time_str, ",");
/* For each day/timerange set, */
while (p) {
/* Store valid days */
te->days = 0;
while (isalpha(*p)) {
if (!p[1] || !isalpha(p[1])) {
syslog(LOG_ERR,
"Malformed day name (%c%c) in time field of config file (%s). Entry ignored.",
p[0], p[1], CONFIG);
(*t)->days = 0;
return;
}
*p = toupper(*p);
p[1] = toupper(p[1]);
i = 0;
while (daynames[i]) {
if (!strncmp(daynames[i], p, 2)) {
te->days |= daynums[i];
break;
}
i++;
}
if (!daynames[i]) {
syslog(LOG_ERR,
"Malformed day name (%c%c) in time field of config file (%s). Entry ignored.",
p[0], p[1], CONFIG);
(*t)->days = 0;
return;
}
p += 2;
}
/* Store start and end times */
if (*p) {
if (strlen(p) != 9 || p[4] != '-') {
syslog(LOG_ERR,
"Malformed time (%s) in time field of config file (%s). Entry ignored.", p,
CONFIG);
(*t)->days = 0;
return;
}
te->starttime = atoi(p);
te->endtime = atoi(p + 5);
if ((te->starttime == 0 && strncmp(p, "0000-", 5))
|| (te->endtime == 0 && strcmp(p + 5, "0000"))) {
syslog(LOG_ERR,
"Invalid range (%s) in time field of config file (%s). Entry ignored.", p,
CONFIG);
(*t)->days = 0;
return;
}
} else {
te->starttime = 0;
te->endtime = 2359;
}
p = strtok(NULL, ",");
te++;
}
te->days = 0;
}
void alloc_cp(a, b)
char **a;
char *b;
{
if ((*a = (char *) malloc(strlen(b) + 1)) == NULL)
bailout("Out of memory", 1);
else
strcpy(*a, b);
}
void read_config()
{
FILE *config_file;
char *p;
char *lstart;
int i = 0;
#ifdef DEBUG
int j = 0;
#endif
char line[256];
char *tok;
int linenum = 0;
if ((config_file = fopen(CONFIG, "r")) == NULL)
bailout("Cannot open config file", 1);
while (fgets(line, 256, config_file) != NULL) {
linenum++;
p = line;
while (*p && (*p == ' ' || *p == '\t'))
p++;
lstart = p;
while (*p && *p != '#' && *p != '\n')
p++;
*p = '\0';
if (*lstart) {
if (i == MAXLINES)
bailout("Too many lines in timeouts config file.", 1);
if ((config[i] = (struct config_ent *) malloc(sizeof(struct config_ent))) == NULL)
bailout("Out of memory", 1);
config[i]->times = NULL;
config[i]->ttys = NULL;
config[i]->users = NULL;
config[i]->groups = NULL;
config[i]->login_allowed = 1;
config[i]->idlemax = -1;
config[i]->sessmax = -1;
config[i]->daymax = -1;
config[i]->warntime = 5;
config[i]->messages[IDLEMSG] = NULL;
config[i]->messages[SESSMSG] = NULL;
config[i]->messages[DAYMSG] = NULL;
config[i]->messages[NOLOGINMSG] = NULL;
if ((tok = strsep(&lstart, ":")) != NULL)
store_times(&config[i]->times, tok);
if ((tok = strsep(&lstart, ":")) != NULL)
alloc_cp(&config[i]->ttys, tok);
if ((tok = strsep(&lstart, ":")) != NULL)
alloc_cp(&config[i]->users, tok);
if ((tok = strsep(&lstart, ":")) != NULL)
alloc_cp(&config[i]->groups, tok);
tok = strsep(&lstart, ":");
if (tok != NULL && !strncasecmp(tok, "NOLOGIN", 7)) {
config[i]->login_allowed = 0;
if (tok[7] == ';')
alloc_cp(&config[i]->messages[NOLOGINMSG], tok + 8);
else if ((tok = strsep(&lstart, ":")) != NULL)
alloc_cp(&config[i]->messages[NOLOGINMSG], tok);
} else if (tok != NULL && !strcasecmp(tok, "LOGIN"))
config[i]->login_allowed = 1;
else {
if (tok != NULL) {
config[i]->idlemax = atoi(tok);
if ((p = strchr(tok, ';')) != NULL)
alloc_cp(&config[i]->messages[IDLEMSG], p + 1);
}
if ((tok = strsep(&lstart, ":")) != NULL) {
config[i]->sessmax = atoi(tok);
if ((p = strchr(tok, ';')) != NULL)
alloc_cp(&config[i]->messages[SESSMSG], p + 1);
}
if ((tok = strsep(&lstart, ":")) != NULL) {
config[i]->daymax = atoi(tok);
if ((p = strchr(tok, ';')) != NULL)
alloc_cp(&config[i]->messages[DAYMSG], p + 1);
}
if ((tok = strsep(&lstart, ":")) != NULL) {
config[i]->warntime = atoi(tok);
}
}
if (!config[i]->times || !config[i]->ttys || !config[i]->users || !config[i]->groups) {
syslog(LOG_ERR, "Error on line %d of config file (%s). Line ignored.", linenum,
CONFIG);
} else
i++;
}
}
config[i] = NULL;
if (fclose(config_file) == EOF)
bailout("Cannot close config file", 1);
#ifdef DEBUG
i = 0;
while (config[i]) {
printf("line %d: ", i);
j = 0;
while (config[i]->times[j].days)
printf("%d(%d-%d):", config[i]->times[j].days,
config[i]->times[j].starttime, config[i]->times[j].endtime), j++;
printf("%s:%s:%s:%s:%d;%s:%d;%s:%d;%s:%d\n",
config[i]->ttys,
config[i]->users,
config[i]->groups,
config[i]->login_allowed ? "LOGIN" : "NOLOGIN",
config[i]->idlemax,
config[i]->messages[IDLEMSG] ==
NULL ? "builtin" : config[i]->messages[IDLEMSG],
config[i]->sessmax,
config[i]->messages[SESSMSG] ==
NULL ? "builtin" : config[i]->messages[SESSMSG],
config[i]->daymax,
config[i]->messages[DAYMSG] == NULL ? "builtin" : config[i]->messages[DAYMSG],
config[i]->warntime), i++;
}
printf("End debug output.\n");
#endif
}
char chktimes(te)
struct time_ent *te;
{
while (te->days) {
if (daynums[now.tm_wday] & te->days && /* Date within range */
((te->starttime <= te->endtime && /* Time within range */
now_hhmm >= te->starttime && now_hhmm <= te->endtime)
|| (te->starttime > te->endtime
&& (now_hhmm >= te->starttime || now_hhmm <= te->endtime))))
return 1;
te++;
}
return 0;
}
char chkmatch(element, in_set)
char *element;
char *in_set;
{
char *t;
char *set = (char *) malloc(strlen(in_set) + 1);
if (set == NULL)
bailout("Out of memory", 1);
else
strcpy(set, in_set);
t = strtok(set, " ,");
while (t) {
if (t[strlen(t) - 1] == '*') {
if (!strncmp(t, element, strlen(t) - 1)) {
free(set);
return 1;
}
} else if (!strcmp(t, element)) {
free(set);
return 1;
}
t = strtok(NULL, " ,");
}
free(set);
return 0;
}
/* Return the number of minutes which user has been logged in for on
* any of the ttys specified in config[configline] during the current day.
*/
void get_day_time(user)
char *user;
{
struct ut_list *login_p;
struct ut_list *logout_p;
struct ut_list *prev_p;
daytime = 0;
login_p = wtmplist;
while (login_p)
{
/* For each login on a matching tty find its logout */
if (login_p->elem.ut_type == USER_PROCESS && !strncmp(login_p->elem.ut_user, user, 8)
&& chkmatch(login_p->elem.ut_line, config[configline]->ttys)) {
#ifdef DEBUG_WTMP
struct tm *tm;
tm = localtime(&(login_p->elem.ut_time));
fprintf(stderr, "%d:%d %s %s %s\n",
tm->tm_hour, tm->tm_min, login_p->elem.ut_line, login_p->elem.ut_user, "login");
#endif
prev_p = logout_p = login_p->next;
while (logout_p) {
/*
* SA19931128
* If there has been a crash, then be reasonably fair and use the
* last recorded login/logout as the user's logout time. This will
* potentially allow them slightly more online time than usual,
* but is better than marking them as logged in for the time the machine
* was down.
*/
if (logout_p->elem.ut_type == BOOT_TIME) {
logout_p = prev_p;
break;
}
if (!strncmp(login_p->elem.ut_line, logout_p->elem.ut_line, UT_LINESIZE))
break;
prev_p = logout_p;
logout_p = logout_p->next;
}
#ifdef DEBUG_WTMP
if (logout_p) {
tm = localtime(&(logout_p->elem.ut_time));
fprintf(stderr, "%d:%d %s %s %s\n",
tm->tm_hour, tm->tm_min, logout_p->elem.ut_line, logout_p->elem.ut_user,
"logout");
fprintf(stderr, "%s %d minutes\n", user,
((logout_p ? logout_p->elem.ut_time : time_now) -
login_p->elem.ut_time) / 60);
}
#endif
daytime += (logout_p ? logout_p->elem.ut_time : time_now) - login_p->elem.ut_time;
}
login_p = login_p->next;
}
daytime /= 60;
#ifdef DEBUG
fprintf(stderr, "%s has been logged in for %d minutes today.\n", user, daytime);
#endif
return;
}
void warnpending(tty, time_remaining, user, host)
char *tty;
int time_remaining;
char *user;
char *host;
{
int fd;
FILE *ttyf;
char cmdbuf[1024];
#ifdef DEBUG
syslog(LOG_DEBUG, "Warning %s@%s on %s of pending logoff in %d minutes.", user, host, tty,
time_remaining);
#endif
if (chk_xsession(tty, host)) {
syslog(LOG_DEBUG,
"Warning %s running X on %s for pending logout! (%d min%s left)",
user, tty, time_remaining, time_remaining == 1 ? "" : "s");
/* then send the message using xmessage */
/* well, this is not really clean: */
sprintf(cmdbuf,
"su %s -c \"xmessage -display %s -center 'WARNING: You will be logged out in %d minute%s when your %s limit expires.'&\"",
user, host, time_remaining, time_remaining == 1 ? "" : "s",
limit_names[limit_type]);
system(cmdbuf);
#ifdef DEBUG
syslog(LOG_DEBUG, "cmdbuf=%s", cmdbuf);
#endif
sleep(KWAIT); /* and give the user some time to read the message ;) */
return;
}
if ((fd = open(tty, O_WRONLY | O_NOCTTY | O_NONBLOCK)) < 0 || (ttyf = fdopen(fd, "w")) == NULL) {
syslog(LOG_ERR, "Could not open %s to warn of impending logoff.\n", tty);
return;
}
fprintf(ttyf,
"\a\r\nWARNING:\r\nYou will be logged out in %d minute%s when your %s limit expires.\r\n",
time_remaining, time_remaining == 1 ? "" : "s", limit_names[limit_type]);
fclose(ttyf);
}
char chk_timeout(user, dev, host, idle, session)
char *user;
char *dev;
char *host;
int idle;
int session;
{
struct passwd *pw;
struct group *gr;
struct group *secgr;
char timematch = 0;
char ttymatch = 0;
char usermatch = 0;
char groupmatch = 0;
char **p;
configline = 0;
/* Find primary group for specified user */
if ((pw = getpwnam(user)) == NULL) {
syslog(LOG_ERR, "Could not get password entry for %s.", user);
return 0;
}
if ((gr = getgrgid(pw->pw_gid)) == NULL) {
syslog(LOG_ERR, "Could not get group name for %s.", user);
return 0;
}
#ifdef DEBUG
syslog(LOG_DEBUG, "Checking user %s group %s tty %s.", user, gr->gr_name, dev);
#endif
/* Check to see if current user matches any entry based on tty/user/group */
while (config[configline]) {
timematch = chktimes(config[configline]->times);
ttymatch = chkmatch(dev, config[configline]->ttys);
usermatch = chkmatch(user, config[configline]->users);
groupmatch = chkmatch(gr->gr_name, config[configline]->groups);
/* If the primary group doesn't match this entry, check secondaries */
setgrent();
while (!groupmatch && (secgr = getgrent()) != NULL) {
p = secgr->gr_mem;
while (*p && !groupmatch) {
if (!strcmp(*p, user))
groupmatch = chkmatch(secgr->gr_name, config[configline]->groups);
p++;
}
}
/* If so, then check their idle, daily and session times in turn */
if (timematch && ttymatch && usermatch && groupmatch) {
get_day_time(user);
#ifdef DEBUG
syslog(LOG_DEBUG, "Matched entry %d", configline);
syslog(LOG_DEBUG,
"Idle=%d (max=%d) Sess=%d (max=%d) Daily=%d (max=%d) warntime=%d",
idle, config[configline]->idlemax, session,
config[configline]->sessmax, daytime, config[configline]->daymax,
config[configline]->warntime);
#endif
limit_type = NOLOGINMSG;
if (!config[configline]->login_allowed)
return NOLOGIN;
limit_type = IDLEMSG;
if (config[configline]->idlemax > 0 && idle >= config[configline]->idlemax)
return IDLEMAX;
limit_type = SESSMSG;
if (config[configline]->sessmax > 0 && session >= config[configline]->sessmax)
return SESSMAX;
limit_type = DAYMSG;
if (config[configline]->daymax > 0 && daytime >= config[configline]->daymax)
return DAYMAX;
/* If none of those have been exceeded, then warn users of upcoming logouts */
limit_type = DAYMSG;
if (config[configline]->daymax > 0
&& daytime >= config[configline]->daymax - config[configline]->warntime)
warnpending(dev, config[configline]->daymax - daytime, user, host);
else {
limit_type = SESSMSG;
if (config[configline]->sessmax > 0
&& session >= config[configline]->sessmax - config[configline]->warntime)
warnpending(dev, config[configline]->sessmax - session, user, host);
}
/* Otherwise, leave the poor net addict alone */
return ALLOWED;
}
configline++;
}
/* If they do not match any entries, then they can stay on forever */
return ALLOWED;
}
/* Check for exceeded time limits & logoff exceeders */
void check_idle()
{
char user[sizeof(utmpp->ut_user) + 1];
char host[sizeof(utmpp->ut_host) + 1];
struct stat status;
time_t idle, sesstime;
if (utmpp->ut_type != USER_PROCESS || kill(utmpp->ut_pid, 0) == -1) /* if not user process, or if proc doesn't exist */
return; /* skip the utmp entry */
strncpy(user, utmpp->ut_user, sizeof(user) - 1); /* get user name */
user[sizeof(user) - 1] = '\0'; /* null terminate user name string */
strncpy(host, utmpp->ut_host, sizeof(host) - 1); /* get host name */
host[sizeof(host) - 1] = '\0';
strncpy(dev, utmpp->ut_line, sizeof(dev) - 1); /* get device name */
dev[sizeof(dev) - 1] = '\0';
sprintf(path, "/dev/%s", dev);
if (stat(path, &status) && chk_xsession(dev, host) != TIMEOUTD_XSESSION_LOCAL) { /* if can't get status for
port && if it's not a local Xsession */
syslog(LOG_ERR, "Can't get status of user %s's terminal (%s)\n", user, dev);
return;
}
/* idle time is the lesser of:
* current time less last access time OR
* current time less last modified time
*/
#ifdef TIMEOUTDX11
if (chk_xterm(dev, host)) {
return;
} else if (chk_xsession(dev, host)) { /* check idle for Xsession, but not for xterm */
idle = get_xidle(user, host) / 1000 / 60; /* get_xidle returns millisecs, we need mins */
syslog(LOG_DEBUG, "get_xidle(%s,%s) returned %d mins idle for %s.", dev, host,
(int) idle, user);
} else
#endif
idle = (time_now - max(status.st_atime, status.st_mtime)) / 60;
sesstime = (time_now - utmpp->ut_time) / 60;
switch (chk_timeout(user, dev, host, idle, sesstime)) {
case ALLOWED:
#ifdef DEBUG
syslog(LOG_DEBUG, "User %s passed all checks.", user);
#endif
break;
case IDLEMAX:
syslog(LOG_NOTICE,
"User %s exceeded idle limit (idle for %ld minutes, max=%d).\n",
user, idle, config[configline]->idlemax);
killit(utmpp->ut_pid, user, dev, host);
break;
case SESSMAX:
syslog(LOG_NOTICE,
"User %s exceeded maximum session limit at %s (on for %ld minutes, max=%d).\n",
user, dev, sesstime, config[configline]->sessmax);
killit(utmpp->ut_pid, user, dev, host);
break;
case DAYMAX:
syslog(LOG_NOTICE,
"User %s exceeded maximum daily limit (on for %d minutes, max=%d).\n",
user, daytime, config[configline]->daymax);
killit(utmpp->ut_pid, user, dev, host);
break;
case NOLOGIN:
#ifdef DEBUG
syslog(LOG_NOTICE, "NOLOGIN period reached for user %s@%s. (pid %d)", user, host,
utmpp->ut_pid);
#else
syslog(LOG_NOTICE, "NOLOGIN period reached for user %s %s", user, host);
#endif
killit(utmpp->ut_pid, user, dev, host);
break;
default:
syslog(LOG_ERR, "Internal error - unexpected return from chk_timeout");
}
}
void bailout(message, status) /* display error message and exit */
int status;
char *message;
{
syslog(LOG_ERR, "Exiting - %s", message);
exit(status);
}
void shut_down(signum)
int signum;
{
syslog(LOG_NOTICE, "Received SIGTERM.. exiting.");
exit(0);
}
void segfault(signum)
int signum;
{
syslog(LOG_NOTICE, "Received SIGSEGV.. Something went wrong! Exiting!");
exit(0);
}
void logoff_msg(tty)
int tty;
{
FILE *msgfile = NULL;
char msgbuf[1024];
int cnt;
if (config[configline]->messages[limit_type])
msgfile = fopen(config[configline]->messages[limit_type], "r");
if (msgfile) {
while ((cnt = read(tty, msgbuf, 1024)) > 0)
write(tty, msgbuf, cnt);
fclose(msgfile);
} else {
if (limit_type == NOLOGINMSG)
sprintf(msgbuf,
"\a\r\n\r\nLogins not allowed at this time. Please try again later.\r\n");
else
sprintf(msgbuf,
"\a\r\n\r\nYou have exceeded your %s time limit. Logging you off now.\r\n\r\n\a",
limit_names[limit_type]);
write(tty, msgbuf, strlen(msgbuf));
}
}
/* terminate process using SIGHUP, then SIGKILL */
void killit(pid, user, dev, host)
int pid;
char *user;
char *dev;
char *host;
{
int tty;
if (chk_xsession(dev, host) && !chk_xterm(dev, host)) {
killit_xsession(utmpp->ut_pid, user, host);
return;
}
/* Tell user which limit they have exceeded and that they will be logged off */
if ((tty = open(dev, O_WRONLY | O_NOCTTY | O_NONBLOCK)) < 0) {
syslog(LOG_ERR, "Could not write logoff message to %s.", dev);
return;
}
#ifdef DEBUG
syslog(LOG_NOTICE, "I am at killit() pid=%d user=%s line %d", pid, user, __LINE__);
#endif
logoff_msg(tty);
sleep(KWAIT); /*make sure msg does not get lost, again (esp. ssh) */
close(tty);
#ifdef DEBUG
syslog(LOG_NOTICE, "Would normally kill pid %d user %s on %s", pid, user, dev);
return;
#endif
if (fork()) /* the parent process */
return; /* returns */
/* Wait a little while in case the above message gets lost during logout */
kill(pid, SIGHUP); /* first send "hangup" signal */
sleep(KWAIT);
if (!kill(pid, 0)) { /* SIGHUP might be ignored */
kill(pid, SIGKILL); /* then send sure "kill" signal */
sleep(KWAIT);
if (!kill(pid, 0)) {
syslog(LOG_ERR, "Could not log user %s off line %s.", user, dev);
}
}
exit(0);
}
void reread_config(signum)
int signum;
{
int i = 0;
if (!allow_reread)
pending_reread = 1;
else {
pending_reread = 0;
syslog(LOG_NOTICE, "Re-reading configuration file.");
while (config[i]) {
free(config[i]->times);
free(config[i]->ttys);
free(config[i]->users);
free(config[i]->groups);
if (config[i]->messages[IDLEMSG])
free(config[i]->messages[IDLEMSG]);
if (config[i]->messages[DAYMSG])
free(config[i]->messages[DAYMSG]);
if (config[i]->messages[SESSMSG])
free(config[i]->messages[SESSMSG]);
if (config[i]->messages[NOLOGINMSG])
free(config[i]->messages[NOLOGINMSG]);
free(config[i]);
i++;
}
read_config();
}
signal(SIGHUP, reread_config);
}
void reapchild(signum)
int signum;
{
int st;
wait(&st);
signal(SIGCHLD, reapchild);
}
int chk_xsession(dev, host) /* returns TIMEOUTD_XSESSION_{REMOTE,LOCAL,NONE} when dev and host seem to be a xSession. */
char *dev, *host;
{
if (strncmp(host, ":0", 1) == 0) {
/* Look here, how we check if it's a Xsession but no telnet or whatever.
* The problem is that a xterm running on :0 has the device pts/?. But if we ignore
* all pts/?, ssh users won't be restricted.
* So, if (tty="pts/?" OR tty=":*") AND host = ":*", we have a Xsession:
*
* seppy@schleptop:~$ w
* 20:06:33 up 18 min, 6 users, load average: 0.14, 0.16, 0.12
* USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
* dennis :0 - 19:48 ?xdm? 0.00s ? -
* dennis pts/1 :0.0 20:00 4:12 0.03s 0.03s bash
* dennis pts/2 :0.0 20:01 0.00s 0.18s 0.16s ssh localhost
* dennis pts/3 localhost 20:01 0.00s 0.01s 0.00s w
*/
#ifdef DEBUG
syslog(LOG_DEBUG, "LOCAL Xsession detected. device=%s host=%s", dev, host);
#endif
return TIMEOUTD_XSESSION_LOCAL;
} else if (strstr(dev, ":") && strlen(host) > 1 && gethostbyname(host)) {
/* What about remote XDMCP sessions?
* USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
* mark pts/3 mercury Sat11 0.00s 10.99s 0.04s w
* rebecca ap:10 ap 10:32 0.00s 0.00s 1.28s x-session-manager
*/
#ifdef DEBUG
syslog(LOG_DEBUG, "REMOTE Xsession detected. device=%s host=%s", dev, host);
#endif
return TIMEOUTD_XSESSION_REMOTE;
} else {
#ifdef DEBUG
syslog(LOG_DEBUG, "NO xsession detected. device=%s host=%s", dev, host);
#endif
return TIMEOUTD_XSESSION_NONE;
}
}
/* We have to handle Xterms(pts/?) and Xsessions (:0) different:
- Check Xsession for idle, but not a XTERM
- Send message for pending logoff to X, but not to XTERM
-> Don't check XTERM at all
- but: check ssh (pts/?) but no XTERM (again)
*/
int chk_xterm(dev, host) /* returns 1 when dev and host seem to be a xTERM. */
char *dev, *host;
{
if (strncmp(dev, "pts/0", 3) == 0 && strncmp(host, ":0", 1) == 0) {
#ifdef DEBUG
syslog(LOG_DEBUG, "XTERM detected. device=%s host=%s Ignoring.", dev, host);
#endif
return 1;
} else
return 0;
}
void killit_xsession(pid, user, host)
int pid;
char *host, *user;
{
char msgbuf[512], cmdbuf[1024];
/* first, get the message into msgbuf */
if (limit_type == NOLOGINMSG) {
sprintf(msgbuf, "Logins not allowed at this time. Please try again later.");
} else {
sprintf(msgbuf, "You have exceeded your %s time limit. Logging you off now.",
limit_names[limit_type]);
}
/* then send the message using xmessage */
/* well, this is not really clean: */
sprintf(cmdbuf, "su %s -c \"xmessage -display %s -center '%s'&\"", user, host, msgbuf);
system(cmdbuf);
#ifdef DEBUG
syslog(LOG_DEBUG, "cmdbuf=%s", cmdbuf);
#endif
sleep(KWAIT); /* and give the user some time to read the message ;) */
#ifndef DEBUG
/* kill pid here */
kill(pid, SIGTERM); /* otherwise, X crashes */
sleep(KWAIT);
if (!kill(pid, 0)) { /* SIGHUP might be ignored */
kill(pid, SIGKILL); /* then send sure "kill" signal */
sleep(KWAIT);
if (!kill(pid, 0)) {
syslog(LOG_ERR, "Could not log user %s off line %s. (running X)", user, host);
}
}
#else
syslog(LOG_ERR, "Would normally logoff user %s running X (kill PID %d)", user, pid);
#endif
}
#ifdef TIMEOUTDX11
Time get_xidle(user, display) /*seppy; returns millicecs since last input event */
char *user;
char *display;
{
Display *dpy;
static XScreenSaverInfo *mitInfo = 0;
struct passwd *pwEntry;
char homedir[50]; /*50 should be enough */
char oldhomedir[50];
uid_t oldeuid;
Time retval = 0;
pwEntry = getpwnam(user);
if (!pwEntry) {
syslog(LOG_ERR, "Could not get passwd-entry for user %s", user);
}
#ifdef DEBUG
syslog(LOG_DEBUG, "su-ing to %s(%d) and connecting to X", user, pwEntry->pw_uid);
#endif
/*change into the user running x. we need that to connect to X */
/*save old, to come back */
oldeuid = geteuid();
sprintf(oldhomedir, "HOME=%s", getenv("HOME"));
/*become user */
if (seteuid(pwEntry->pw_uid) == -1) {
syslog(LOG_ERR, "Could not seteuid(%d).", pwEntry->pw_uid);
}
sprintf(homedir, "HOME=%s", pwEntry->pw_dir);
putenv(homedir);
/* First, check if there is a xserver.. */
if ((dpy = XOpenDisplay(display)) == NULL) {
syslog(LOG_NOTICE, "Could not connect to %s to query idle-time for %s. Ignoring.", display,
user);
} else {
if (!mitInfo)
mitInfo = XScreenSaverAllocInfo();
XScreenSaverQueryInfo(dpy, DefaultRootWindow(dpy), mitInfo);
retval = mitInfo->idle;
XCloseDisplay(dpy);
}
/*go back again */
putenv(oldhomedir);
setuid(oldeuid);
return retval;
}
#endif