/* "@(#) 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef TIMEOUTDX11 #include #include #include #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 */ struct utmp *getutent(); /* returns next 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; } void check_idle() { /* Check for exceeded time limits & logoff exceeders */ 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