/* Copyright (C) 2016 Centro de Computacao Cientifica e Software Livre * Departamento de Informatica - Universidade Federal do Parana - C3SL/UFPR * * This file is part of simmc-agent * * last.c Re-implementation of the 'last' command, this time * for Linux. Yes I know there is BSD last, but I * just felt like writing this. No thanks :-). * Also, this version gives lots more info (especially with -x) * * Author: Miquel van Smoorenburg, miquels@cistron.nl * * Version: @(#)last 2.85 30-Jul-2004 miquels@cistron.nl * * This file is part of the sysvinit suite, * Copyright (C) 1991-2004 Miquel van Smoorenburg. * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "agent/linux/get_user_count.h" #include <string> #include <set> /* Global variables */ struct utmplist *utmplist = NULL; char *ufile; /* Filename of this file */ time_t lastdate; /* Last date we've seen */ struct tm * timeinfo; set<string> users; char yesterday[7], today[7]; /* * Read one utmp entry, return in new format. * Automatically reposition file pointer. */ static int uread(FILE *fp, struct utmp *u, int *quit) { static int utsize, bpos; static char buf[UCHUNKSIZE]; char tmp[1024]; static off_t fpos; off_t o; if (quit == NULL && u != NULL) { /* * Normal read. */ return fread(u, sizeof(struct utmp), 1, fp); } if (u == NULL) { /* * Initialize and position. */ utsize = sizeof(struct utmp); fseeko(fp, 0, SEEK_END); fpos = ftello(fp); if (fpos == 0) return 0; o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE; if (fseeko(fp, o, SEEK_SET) < 0) { fprintf(stderr, "ERROR: seek failed!\n"); return 0; } bpos = static_cast<int>(fpos - o); if (fread(buf, bpos, 1, fp) != 1) { fprintf(stderr, "ERROR: read failed!\n"); return 0; } fpos = o; return 1; } /* * Read one struct. From the buffer if possible. */ bpos -= utsize; if (bpos >= 0) { memcpy(u, buf + bpos, sizeof(struct utmp)); return 1; } /* * Oops we went "below" the buffer. We should be able to * seek back UCHUNKSIZE bytes. */ fpos -= UCHUNKSIZE; if (fpos < 0) return 0; /* * Copy whatever is left in the buffer. */ memcpy(tmp + (-bpos), buf, utsize + bpos); if (fseeko(fp, fpos, SEEK_SET) < 0) { perror("fseek"); return 0; } /* * Read another UCHUNKSIZE bytes. */ if (fread(buf, UCHUNKSIZE, 1, fp) != 1) { perror("fread"); return 0; } /* * The end of the UCHUNKSIZE byte buffer should be the first * few bytes of the current struct utmp. */ memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos); bpos += UCHUNKSIZE; memcpy(u, tmp, sizeof(struct utmp)); return 1; } static void list(struct utmp *p, time_t t, int what) { time_t tmp; struct user_log * u = (struct user_log *)malloc(sizeof(struct user_log)); char login[25], logout[25], month[3]; tmp = (time_t)p->ut_time; timeinfo = gmtime(&tmp); // NOLINT(runtime/threadsafe_fn) strftime(month, 3, "%m", timeinfo); month[3]='\0'; snprintf(u->user, sizeof(u->user), p->ut_name, 0); snprintf(login, sizeof(login), ctime(&tmp), 0); u->login[0] = login[22]; u->login[1] = login[23]; u->login[2] = month[0]; u->login[3] = month[1]; u->login[4] = login[8] == ' '?'0':login[8]; u->login[5] = login[9]; u->login[6] = '\0'; switch (what) { case R_NOW: snprintf(u->logout, sizeof(u->logout), "999999"); break; case R_PHANTOM: snprintf(u->logout, sizeof(u->logout), "000000"); break; case R_NORMAL: timeinfo = gmtime(&t); // NOLINT(runtime/threadsafe_fn) strftime(month, 3, "%m", timeinfo); month[3]='\0'; snprintf(logout, sizeof(logout), ctime(&t), 0); u->logout[0] = logout[22]; u->logout[1] = logout[23]; u->logout[2] = month[0]; u->logout[3] = month[1]; u->logout[4] = logout[8] == ' '?'0':logout[8]; u->logout[5] = logout[9]; u->logout[6] = '\0'; break; } if (!strcmp(u->logout, yesterday)) { users.insert(u->user); } else if (!strcmp(u->login, yesterday)) { users.insert(u->user); } else if (( strcmp(u->login, yesterday) && strcmp(u->login, today)) && (!strcmp(u->logout, today) || !strcmp(u->logout, "999999"))) { users.insert(u->user); } } static struct user_log * last_modified() { FILE *fp; /* Filepointer of wtmp file */ struct utmp ut; /* Current utmp entry */ struct utmp oldut; /* Old utmp entry to check for duplicates */ struct utmplist *p, *next; /* Pointer into utmplist */ time_t lastboot = 0; /* Last boottime */ time_t lastdown; /* Last downtime */ time_t begintime; /* When wtmp begins */ int c, x; /* Scratch */ int skip = 1; struct stat st; /* To stat the [uw]tmp file */ int quit = 0; /* Flag */ struct user_log * u = NULL; struct user_log * v = NULL; struct user_log * w = NULL; /* * Which file do we want to read? */ ufile = (char *)(WTMP_FILE); time(&lastdown); /* * Fill in 'lastdate' */ lastdate = lastdown; /* * Open the utmp file */ if ((fp = fopen(ufile, "r")) == NULL) { x = errno; if (x == ENOENT) // file not found throw std::string(ufile) + std::string(": ") + std::string(strerror(errno)); throw strerror(errno); } /* * Optimize the buffer size. */ setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE); /* * Read first structure to capture the time field */ if (uread(fp, &ut, NULL) == 1) { begintime = ut.ut_time; } else { fstat(fileno(fp), &st); begintime = st.st_ctime; quit = 1; } /* * Go to end of file minus one structure * and/or initialize utmp reading code. */ uread(fp, NULL, NULL); /* * Read struct after struct backwards from the file. */ while (!quit && skip) { if (uread(fp, &ut, &quit) != 1) break; if (memcmp(&ut, &oldut, sizeof(struct utmp)) == 0) continue; memcpy(&oldut, &ut, sizeof(struct utmp)); lastdate = ut.ut_time; /* * Set ut_type to the correct type. */ if (strncmp(ut.ut_line, "~", 1) == 0) { if (strncmp(ut.ut_user, "shutdown", 8) == 0) ut.ut_type = SHUTDOWN_TIME; else if (strncmp(ut.ut_user, "reboot", 6) == 0) ut.ut_type = BOOT_TIME; else if (strncmp(ut.ut_user, "runlevel", 8) == 0) ut.ut_type = RUN_LVL; /*def COMPAT*/ /* * For stupid old applications that don't fill in * ut_type correctly. */ } else { if (ut.ut_type != DEAD_PROCESS && ut.ut_name[0] && ut.ut_line[0] && strcmp(ut.ut_name, "LOGIN") != 0) ut.ut_type = USER_PROCESS; /* * Even worse, applications that write ghost * entries: ut_type set to USER_PROCESS but * empty ut_name... */ if (ut.ut_name[0] == 0) ut.ut_type = DEAD_PROCESS; /* * Clock changes. */ if (strcmp(ut.ut_name, "date") == 0) { if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME; if (ut.ut_line[0] == '{') ut.ut_type = NEW_TIME; } } switch (ut.ut_type) { case USER_PROCESS: /* * This was a login - show the first matching * logout record and delete all records with * the same ut_line. */ c = 0; skip = 0; for (p = utmplist; p; p = next) { next = p->next; if (strncmp(p->ut.ut_line, ut.ut_line, UT_LINESIZE) == 0) { /* Show it */ if (c == 0) { quit = 0; list(&ut, p->ut.ut_time, R_NORMAL); c = 1; } if (p->next) p->next->prev = p->prev; if (p->prev) p->prev->next = p->next; else utmplist = p->next; free(p); } } /* * Not found? Then crashed, down, still * logged in, or missing logout record. */ if (c == 0) { c = R_NOW; /* Is process still alive? */ if (ut.ut_pid > 0 && kill(ut.ut_pid, 0) != 0 && errno == ESRCH) c = R_PHANTOM; quit = 0; list(&ut, lastboot, c); } /* FALLTHRU */ case DEAD_PROCESS: /* * Just store the data if it is * interesting enough. */ if (ut.ut_line[0] == 0) break; p = (struct utmplist *)malloc(sizeof(struct utmplist)); if (p == NULL) { throw std::string("ERROR: out of memory"); } memcpy(&p->ut, &ut, sizeof(struct utmp)); p->next = utmplist; p->prev = NULL; if (utmplist) utmplist->prev = p; utmplist = p; break; } } while (!quit) { if (uread(fp, &ut, &quit) != 1) break; if (memcmp(&ut, &oldut, sizeof(struct utmp)) == 0) continue; memcpy(&oldut, &ut, sizeof(struct utmp)); lastdate = ut.ut_time; /* * Set ut_type to the correct type. */ if (strncmp(ut.ut_line, "~", 1) == 0) { if (strncmp(ut.ut_user, "shutdown", 8) == 0) ut.ut_type = SHUTDOWN_TIME; else if (strncmp(ut.ut_user, "reboot", 6) == 0) ut.ut_type = BOOT_TIME; else if (strncmp(ut.ut_user, "runlevel", 8) == 0) ut.ut_type = RUN_LVL; /*def COMPAT*/ /* * For stupid old applications that don't fill in * ut_type correctly. */ } else { if (ut.ut_type != DEAD_PROCESS && ut.ut_name[0] && ut.ut_line[0] && strcmp(ut.ut_name, "LOGIN") != 0) ut.ut_type = USER_PROCESS; /* * Even worse, applications that write ghost * entries: ut_type set to USER_PROCESS but * empty ut_name... */ if (ut.ut_name[0] == 0) ut.ut_type = DEAD_PROCESS; /* * Clock changes. */ if (strcmp(ut.ut_name, "date") == 0) { if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME; if (ut.ut_line[0] == '{') ut.ut_type = NEW_TIME; } } switch (ut.ut_type) { case USER_PROCESS: /* * This was a login - show the first matching * logout record and delete all records with * the same ut_line. */ c = 0; for (p = utmplist; p; p = next) { next = p->next; if (strncmp(p->ut.ut_line, ut.ut_line, UT_LINESIZE) == 0) { /* Show it */ if (c == 0) { quit = 0; list(&ut, p->ut.ut_time, R_NORMAL); } if (p->next) p->next->prev = p->prev; if (p->prev) p->prev->next = p->next; else utmplist = p->next; free(p); } } /* * Not found? Then crashed, down, still * logged in, or missing logout record. */ if (c == 0) { c = R_NOW; /* Is process still alive? */ if (ut.ut_pid > 0 && kill(ut.ut_pid, 0) != 0 && errno == ESRCH) c = R_PHANTOM; quit = 0; list(&ut, lastboot, c); } /* FALLTHRU */ case DEAD_PROCESS: /* * Just store the data if it is * interesting enough. */ if (ut.ut_line[0] == 0) break; p = (struct utmplist *)malloc(sizeof(struct utmplist)); if (p == NULL) { throw std::string("ERROR: out of memory"); } memcpy(&p->ut, &ut, sizeof(struct utmp)); p->next = utmplist; p->prev = NULL; if (utmplist) utmplist->prev = p; utmplist = p; break; } } fclose(fp); } int get_user_count() { /* The agent execution depends on the file '/var/log/wtmp', which is not * present on some distros, such as OpenSUSE docker image. When this happens, * this file is created and it's permissions setted. */ std::string wtmp_path = "/var/log/wtmp"; struct stat buffer; if (stat(wtmp_path.c_str(), &buffer) != 0) { //file not exists ofstream wtmp_file; wtmp_file.open(wtmp_path.c_str()); wtmp_file.close(); chown(wtmp_path.c_str(), getpwnam("root")->pw_uid, getgrnam("tty")->gr_gid); chmod(wtmp_path.c_str(), 664); } time_t rawtime; time(&rawtime); timeinfo = localtime(&rawtime); // NOLINT(runtime/threadsafe_fn) strftime(today, 7, "%y%m%d", timeinfo); today[6]='\0'; timeinfo->tm_mday--; mktime(timeinfo); strftime(yesterday, 7, "%y%m%d", timeinfo); yesterday[6] = '\0'; last_modified(); return users.size(); }