/* "@(#) 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. */ // modifications by Bill Feero, Logical Solutions /* $Log: timeoutd.c,v $ Revision 1.6 2003/08/28 19:34:31 billf added support for the helper script timeout.sh - needed the helper to kill tasks. ssh and non-bash shells were creating a sshd task that did not pass signals down to its children. /var/run/utmp had the pid of that sshd task as the user parent task, but killing it did not end the session. The helper app kills all tasks that have the controlling tty file open (utmp had that part right) Revision 1.5 2003/07/08 17:50:16 billf Changed the sleep period from 60 seconds to 30 seconds. We were off by up to a minute with the 60 second sleep. Revision 1.4 2003/05/02 15:13:00 billf added a CVS log moved the fork in killit to the top, so the child prints the logoff message and then sleeps. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _GNU_SOURCE #include #include #include #include "cs_support.h" //#define DEBUG #define OPENLOG_FLAGS LOG_CONS|LOG_PID #define SYSLOG_DEBUG LOG_DEBUG /* For those systems (SUNOS) which don't define this: */ #ifndef WTMP_FILE #define WTMP_FILE "/var/log/wtmp" #endif #ifdef SUNOS #define ut_pid ut_time #define ut_user ut_name #define SEEK_CUR 1 #define SEEK_END 2 #define SURE_KILL 1 FILE *utfile = NULL; #define NEED_UTMP_UTILS #define NEED_STRSEP #endif static void read_config (void); static void process_flags (int argc, char **argv); static void usage (int exit_code, const char *who); static void check_idle (void); static void bailout (char *message, int status); static void warnpending(char *tty, int time_remaining, int type); static void killit (int pid, char *user, char *dev); char *ctime(); /* returns pointer to time string */ struct utmp *getutent(); /* returns next utmp file entry */ void shutdown(); void reread_config(); void reapchild(); void free_wtmp(); void read_wtmp(); char chk_timeout(); void logoff_msg(); #ifdef NEED_UTMP_UTILS void setutent() { if (utfile == NULL) { if ((utfile = fopen("/var/run/utmp", "r")) == NULL) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Could not open /var/run/utmp"); closelog(); exit(1); } } else fseek(utfile, 0L, 0); } struct utmp *getutent() /* returns next utmp file entry */ { static struct utmp uent; while (fread(&uent, sizeof(struct utmp), 1, utfile) == 1) { if (uent.ut_line[0] != 0 && uent.ut_name[0] != 0) return &uent; } return (struct utmp *) NULL; } #endif #ifndef CONFIG #define CONFIG "/etc/timeouts" #endif #define MAXLINES 512 #define max(a,b) ((a)>(b)?(a):(b)) #define ACTIVE 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 ut_list { struct utmp elem; struct ut_list *next; }; struct ut_list *wtmplist = (struct ut_list *) NULL; struct ut_list *ut_list_p; 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[10]; }; struct config_ent *config[MAXLINES + 1]; char errmsg[256]; char dev[256]; int 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 */ #ifdef NEED_STRCASECMP int strcasecmp(char *s1, char *s2) { while (*s1 && *s2) { if (tolower(*s1) < tolower(*s2)) return -1; else if (tolower(*s1) > tolower(*s2)) return 1; s1++; s2++; } if (*s1) return -1; if (*s2) return 1; return 0; } #endif #ifdef NEED_STRSEP char *strsep (stringp, delim) char **stringp; char *delim; { char *retp = *stringp; char *p; if (!**stringp) return NULL; while (**stringp) { p = delim; while (*p) { if (*p == **stringp) { **stringp = '\0'; (*stringp)++; return retp; } p++; } (*stringp)++; } return retp; } #endif // display options static void usage (int exit_code, const char *who) { FILE *where; where = (exit_code == 0 ? stdout : stderr); fprintf (where, "Usage: %s [options]\n", who); fputs ("-v|--version show version information\n", where); fputs ("-h|--help show this help information\n", where); exit (exit_code); } /* * process_flags - perform command line argument setting */ static void process_flags(int argc, char **argv) { int arg; struct option long_options[]={ {"help" , 0, NULL, 'h'}, {"version" , 0, NULL, 'v'}, {NULL,0,NULL,0} }; while ((arg = getopt_long(argc, argv, "vh", long_options, NULL)) != EOF) { switch (arg) { case 'v': print_revision_number (stdout, basename (argv[0]), "$Revision: 1.6 $"); exit (0); break; case 'h': usage (0, basename (argv[0])); break; default: usage (1, basename (argv[0])); /* never get here */ break; } } } int main(int argc, char *argv[]) { struct rlimit limits; int num_files, i; process_flags (argc, argv); signal(SIGTERM, shutdown); signal(SIGHUP, reread_config); signal(SIGCHLD, reapchild); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); /* The only valid invocations are "timeoutd" or "timeoutd user tty" */ if (argc != 1 && argc != 3) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Incorrect invocation of timeoutd (argc=%d) by UID %d.", argc, getuid()); closelog(); exit(5); } /* read config file into memory */ read_config(); /* Handle the "timeoutd user tty" invocation */ /* This is a bit of a shameless hack, but, well, it works. */ if (argc == 3) { //openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); /* syslog(SYSLOG_DEBUG, "Running in user check mode. Checking user %s on %s.", argv[1], argv[2]); */ //closelog(); strcpy(dev, "/dev/"); strcat(dev, argv[2]); 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: openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_NOTICE, "User %s on %s exceeded maximum daily limit (%d minutes). Login check failed.", argv[1], argv[2], config[configline]->daymax); closelog(); /* printf("\r\nLogin not permitted. You have exceeded your maximum daily limit.\r\n"); printf("Please try again tomorrow.\r\n"); */ logoff_msg(1); exit(10); case NOLOGIN: openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_NOTICE, "User %s not allowed to login on %s at this time. Login check failed.", argv[1], argv[2]); closelog(); /* printf("\r\nLogin not permitted at this time. Please try again later.\r\n"); */ logoff_msg(1); exit(20); case ACTIVE: //openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); //syslog(SYSLOG_DEBUG, "User %s on %s passed login check.", argv[1], argv[2]); free_wtmp(); //closelog(); exit(0); default: openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Internal error checking user %s on %s - unexpected return from chk_timeout", argv[1], argv[2]); closelog(); exit(30); } } /* If running in daemon mode (no parameters) */ if (fork()) /* the parent process */ exit(0); /* exits */ // fork again to make sure we don't have a controlling tty if (fork()) /* the parent process */ exit(0); /* exits */ // change our curr. working dir. chdir ("/"); // clear our file mode creation mask umask (0); /* close any open file descriptors */ // find out how many file desc. we have if (getrlimit (RLIMIT_NOFILE, &limits) != 0) { exit (-1); // call failed } if (limits.rlim_cur == RLIM_INFINITY) { num_files = 64; // some reasonable value } else { num_files = limits.rlim_cur; } // close those files for (i=0; itm_year != now.tm_year || tm->tm_yday != now.tm_yday) break; #ifndef SUNOS 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) #endif { 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; } /* Position the file pointer 2 structures back */ if (fseek(fp, -2 * sizeof(struct utmp), SEEK_CUR) < 0) break; } fclose(fp); //openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); // syslog(SYSLOG_DEBUG, "Finished reading today's wtmp entries."); //closelog(); } /* Free up memory used by today's wtmp entries */ void free_wtmp() { //openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); //syslog(SYSLOG_DEBUG, "Freeing list of today's wtmp entries."); //closelog(); 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, #ifndef SUNOS wtmplist->elem.ut_type == LOGIN_PROCESS?"login":wtmplist->elem.ut_type == BOOT_TIME?"reboot":"logoff" #else "" #endif ); #endif ut_list_p = wtmplist; wtmplist = wtmplist->next; free(ut_list_p); } //openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); //syslog(SYSLOG_DEBUG, "Finished freeing list of today's wtmp entries."); //closelog(); } 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])) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Malformed day name (%c%c) in time field of config file (%s). Entry ignored.", p[0], p[1], CONFIG); closelog(); (*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]) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Malformed day name (%c%c) in time field of config file (%s). Entry ignored.", p[0], p[1], CONFIG); closelog(); (*t)->days = 0; return; } p += 2; } /* Store start and end times */ if (*p) { if (strlen(p) != 9 || p[4] != '-') { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Malformed time (%s) in time field of config file (%s). Entry ignored.", p, CONFIG); closelog(); (*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"))) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Invalid range (%s) in time field of config file (%s). Entry ignored.", p, CONFIG); closelog(); (*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); } static void read_config (void) { FILE *config_file; char *p; char *lstart; int i = 0; #if 0 #ifdef DEBUG int j = 0; #endif /* DEBUG */ #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, ';'))) alloc_cp(&config[i]->messages[IDLEMSG], p+1); } if ((tok = strsep(&lstart, ":")) != NULL) { config[i]->sessmax = atoi(tok); if ((p = strchr(tok, ';'))) alloc_cp(&config[i]->messages[SESSMSG], p+1); } if ((tok = strsep(&lstart, ":")) != NULL) { config[i]->daymax = atoi(tok); if ((p = strchr(tok, ';'))) 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) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Error on line %d of config file (%s). Line ignored.", linenum, CONFIG); closelog(); } else i++; } } config[i] = NULL; if (fclose(config_file) == EOF) bailout("Cannot close config file", 1); #if 0 #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 /* DEBUG */ #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) // ) // ) 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 ( #ifndef SUNOS login_p->elem.ut_type == USER_PROCESS && #endif !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. */ #ifndef SUNOS if (logout_p->elem.ut_type == BOOT_TIME) { logout_p = prev_p; break; } #endif if (/*logout_p->elem.ut_type == DEAD_PROCESS &&*/ !strcmp(login_p->elem.ut_line, logout_p->elem.ut_line)) 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; return; } static void warnpending(char *tty, int time_remaining, int type) { FILE *ttyf; //openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); //syslog(SYSLOG_DEBUG, "Warning user on %s of pending logoff in %d minutes.", tty, time_remaining); //closelog(); if ((ttyf = fopen(tty, "w")) == NULL) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Could not open %s to warn of impending logoff.\n",tty); closelog(); return; } fprintf(ttyf, "\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[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 *tty = dev + 5; /* Skip over the /dev/ */ char **p; configline = 0; /* Find primary group for specified user */ if ((pw = getpwnam(user)) == NULL) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Could not get password entry for %s.", user); closelog(); return 0; } if ((gr = getgrgid(pw->pw_gid)) == NULL) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Could not get group name for %s.", user); closelog(); return 0; } //openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); //syslog(SYSLOG_DEBUG, "Checking user %s group %s tty %s.", user, gr->gr_name, tty); //closelog(); /* Check to see if current user matches any entry based on tty/user/group */ while (config[configline]) { timematch = chktimes(config[configline]->times); ttymatch = chkmatch(tty, 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) { /* printf("Group %s member %s\n", secgr->gr_name, *p); */ if (!strcmp(*p, user)) groupmatch = chkmatch(secgr->gr_name, config[configline]->groups); p++; } /* free(gr); */ } /* endgrent(); */ /* If so, then check their idle, daily and session times in turn */ if (timematch && ttymatch && usermatch && groupmatch) { get_day_time(user); //openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); //syslog(SYSLOG_DEBUG, "Matched entry %d", configline); //syslog(SYSLOG_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); //closelog(); //disc = getdisc(dev); 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, DAYMSG); else { limit_type = SESSMSG; if (config[configline]->sessmax > 0 && session >= config[configline]->sessmax - config[configline]->warntime) warnpending(dev, config[configline]->sessmax - session, SESSMSG); } /* Otherwise, leave the poor net addict alone */ return ACTIVE; } configline++; } /* If they do not match any entries, then they can stay on forever */ return ACTIVE; } static void check_idle (void) /* Check for exceeded time limits & logoff exceeders */ { char user[128]; char host[128]; struct stat status, *pstat; time_t idle, sesstime, time(); pstat = &status; /* point to status structure */ #ifndef SUNOS if (utmpp->ut_type != USER_PROCESS || !utmpp->ut_user[0]) /* if not user process */ return; /* skip the utmp entry */ #endif 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'; strcpy(dev, "/dev/"); /* add "/dev/" directory prefix */ strncat(dev, utmpp->ut_line, sizeof(dev)-strlen(dev)-1); /* append basename of port */ if (stat(dev, pstat)) /* if can't get status for port */ { sprintf(errmsg, "Can't get status of user %s's terminal (%s)\n", user, dev); openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(SYSLOG_DEBUG, "User %s pid %d dev %s - %s", utmpp->ut_user, utmpp->ut_pid, utmpp->ut_line, errmsg); closelog(); return; // keep running // bailout(errmsg, 1); } /* idle time is the lesser of: * current time less last access time OR * current time less last modified time */ idle = (time_now - max(pstat->st_atime, pstat->st_mtime)) / 60; if (idle < 0) { idle = 0; } sesstime = (time_now - utmpp->ut_time) / 60; switch(chk_timeout(user, dev, host, idle, sesstime)) { case ACTIVE: #ifdef DEBUG openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(SYSLOG_DEBUG, "User %s is active pid %d.", user, utmpp->ut_pid); closelog(); #endif break; case IDLEMAX: openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_NOTICE, "User %s (pid %d) exceeded idle limit (idle for %ld minutes, max=%d).\n", user, utmpp->ut_pid, idle, config[configline]->idlemax); closelog(); killit (utmpp->ut_pid, user, dev); break; case SESSMAX: openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_NOTICE, "User %s (pid %d) exceeded maximum session limit (on for %ld minutes, max=%d).\n", user, utmpp->ut_pid, sesstime, config[configline]->sessmax); closelog(); killit(utmpp->ut_pid, user, dev); break; case DAYMAX: openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_NOTICE, "User %s (pid %d) exceeded maximum daily limit (on for %d minutes, max=%d).\n", user, utmpp->ut_pid, daytime, config[configline]->daymax); closelog(); killit(utmpp->ut_pid, user, dev); break; case NOLOGIN: openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_NOTICE, "NOLOGIN period reached for user %s (pid %d).", user, utmpp->ut_pid); closelog(); killit(utmpp->ut_pid, user, dev); break; default: openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Internal error - unexpected return from chk_timeout"); closelog(); } } static void bailout ( /* display error message and exit */ char *message, /* pointer to the error message */ int status) /* exit status */ { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Exiting - %s", message); closelog(); exit(status); } void shutdown(signum) int signum; { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_NOTICE, "Received SIGTERM.. exiting."); closelog(); 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, "\r\n\r\nLogins not allowed at this time. Please try again later.\r\n"); else sprintf(msgbuf, "\r\n\r\nYou have exceeded your %s time limit. Logging you off now.\r\n\r\n", limit_names[limit_type]); write(tty, msgbuf, strlen(msgbuf)); } } /* ===================================== wmf 8/27/03 at this point I have a pid and a dev name. However, using ssh and a user not using bash as the shell this pid is NOT the one with 'dev' open (in this case I have 3 tasks, 2 sshd and 1 shell, the pid is to the first sshd) I have to find the pid that has dev open and kill that also =====================================*/ /* terminate process using SIGHUP, then SIGKILL */ static void killit (int pid, char *user, char *dev) { int tty; int wait_status; #ifdef SUNOS struct passwd *pw; #endif //wmf I moved the fork to here, because I need to do a sleep // and I don't want the parent doing it if (fork()) /* the parent process */ return; /* returns */ /* 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) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Could not write logoff message to %s.", dev); closelog(); exit (0); //wmf was a return when I was the parent } /* if (!strcmp(limit_name, s_nologin)) fprintf(tty, "\r\n\r\nLogins not allowed at this time. Please try again later.\r\n"); else fprintf(tty, "\r\n\r\nYou have exceeded your %s time limit. Logging you off now.\r\n\r\n", limit_name); */ logoff_msg(tty); close(tty); sleep (5); // give the logoff message time to go out openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_DEBUG, "@%d Kill pid %d user %s on %s", __LINE__, pid, user, dev); closelog(); // wmf 8/28/2003 // kill pid's that have dev open // command = kill `lsof -t {dev}` if (fork() == 0) { // child, run the helper script execl ("/usr/local/sbin/timeout.sh", "/usr/local/sbin/timeout.sh", dev, 0); exit (1); // should not be here } //parent, wait for the child wait (&wait_status); openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_DEBUG, "@%d helper app exited", __LINE__); closelog(); // wmf end mods #ifdef SURE_KILL signal(SIGHUP, SIG_IGN); if ((pw = getpwnam(user)) == NULL) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Could not log user %s off line %s - unable to determine uid.", user, dev); closelog(); } if (setuid(pw->pw_uid)) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Could not log user %s off line %s - unable to setuid(%d).", user, dev, pw->pw_uid); closelog(); } kill(-1, SIGHUP); sleep(KWAIT); kill(-1, SIGKILL); #else kill(pid, SIGTERM); //wmf SIGHUP may be ignored by children, and not passed on // although login will end, this leaves orphans sleep(KWAIT); if (!kill(pid, 0)) { /* SIGTERM might have been ignored */ kill(pid, SIGKILL); /* then send sure "kill" signal */ sleep(KWAIT); if (!kill(pid, 0)) { openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_ERR, "Could not log user %s off line %s.", user, dev); closelog(); } } #endif exit(0); } void reread_config(signum) int signum; { int i = 0; if (!allow_reread) pending_reread = 1; else { pending_reread = 0; openlog("timeoutd", OPENLOG_FLAGS, LOG_DAEMON); syslog(LOG_NOTICE, "Re-reading configuration file."); closelog(); 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); }