/* * * Author: MUrena * Description: A cheesy ad-hoc implementation of last(1). Had to create it mainly because * fwtmp(1M)'s output is a pain to work with. Returns a semicolon delimited field * single line, with ut_user, ut_type, ut_time and ctime(ut_time) of the last time * the user provided in argv[1] logged into the system according to WTMP_FILE. * Return values for main/program: * 0 -> a record was found * 1 -> no record was found * 2 -> WTMP_FILE/Usage error may have occured * * I stole and modified code from Miquel van Smoorenburg's last(1) * Linux implementation. * */ #include #include #include #include #include #define UCHUNKSIZE 16384 /* How much we read at once. */ /* * GLOBALS */ char *progname; /* Name of this program */ /* * show usage */ void usage(char *s) { fprintf(stderr, "Usage: %s \n", s); exit(2); } /* * * I stole and modified this function from Linux's last(1) implementation * created by Miquel van Smoorenburg, miquels@cistron.nl. * * Fancy & fast ;-) utmp read code. * * Read one utmp entry. * Automatically reposition file pointer. * */ int uread(FILE *fp, struct utmp *u) { static int utsize; static char buf[UCHUNKSIZE]; char tmp[1024]; static off_t fpos; static int bpos; off_t o; 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, "%s: seek failed!\n", progname); return 0; } bpos = (int)(fpos - o); if (fread(buf, bpos, 1, fp) != (size_t)1) { fprintf(stderr, "%s: read failed!\n", progname); 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) != (size_t)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; } /* * Get the basename of a filename */ char *mybasename(char *s) { char *p; if ((p = strrchr(s, '/')) != NULL) p++; else p = s; return p; } int main(int argc, char **argv) { char errmsg[BUFSIZ]; FILE *fp; /* Filepointer of wtmp file */ struct utmp ut; /* Current utmp entry */ struct utmp oldut; /* Old utmp entry to check for duplicates */ int r = 1; /* Return values for main/program: */ /* 0 -> a record was found */ /* 1 -> no record was found */ /* 2 -> WTMP_FILE/Usage error may have occured */ progname = mybasename(argv[0]); if (argc != 2) usage(progname); if ( (fp = fopen(WTMP_FILE, "r")) == NULL ) { snprintf(errmsg, sizeof errmsg, "%s: Couldn't open wtmp file `%s' for reading ...", progname, WTMP_FILE); perror(errmsg); exit(2); } /* * Optimize the buffer size. */ setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE); /* * Go to end of file minus one structure * and/or initialize utmp reading code. */ uread(fp, NULL); /* * Read struct after struct backwards from the file. */ while(1) { if (uread(fp, &ut) != 1) break; if (memcmp(&ut, &oldut, sizeof(struct utmp)) == 0) continue; memcpy(&oldut, &ut, sizeof(struct utmp)); /* looking for login records which match the username provided */ if ( strncmp(ut.ut_user, argv[1], sizeof ut.ut_user) == 0 && ut.ut_type == USER_PROCESS ) { printf("%s;%hd;%d;%s", ut.ut_user, ut.ut_type, ut.ut_time, ctime(&ut.ut_time)); r = 0; break; } } if ( fclose(fp) ) { snprintf(errmsg, sizeof errmsg, "%s: Error while closing wtmp file `%s' ...", progname, WTMP_FILE); perror(errmsg); exit(2); } return r; }