From owner-freebsd-hackers Wed Apr 25 23:34:51 2001 Delivered-To: freebsd-hackers@freebsd.org Received: from bazooka.unixfreak.org (bazooka.unixfreak.org [63.198.170.138]) by hub.freebsd.org (Postfix) with ESMTP id CE32837B423 for ; Wed, 25 Apr 2001 23:34:36 -0700 (PDT) (envelope-from dima@unixfreak.org) Received: from spike.unixfreak.org (spike [63.198.170.139]) by bazooka.unixfreak.org (Postfix) with ESMTP id 6654A3E2A for ; Wed, 25 Apr 2001 23:34:36 -0700 (PDT) To: hackers@freebsd.org Subject: Getting a list of users logged in at a certain time (patch) Date: Wed, 25 Apr 2001 23:34:36 -0700 From: Dima Dorfman Message-Id: <20010426063436.6654A3E2A@bazooka.unixfreak.org> Sender: owner-freebsd-hackers@FreeBSD.ORG Precedence: bulk X-Loop: FreeBSD.ORG OpenBSD's last(1) has a nice snapshot feature which allows one to get a list of users logged in at a certain date and time. This is very useful, e.g., for security auditing: you see a message in a log, and want to know who could've caused it. Assuming that it was something that must've been done locally, using this feature you can narrow the list down to a the users that were logged in. It isn't perfect, but it's a start. Attached is a patch which implements this feature in FreeBSD's last(1). I copied most of the "meat" from OpenBSD, but our last(1) still looks nothing like theirs, and the delta is larger than it should've been: surprisingly, our last(1) is quite a bit more mature (e.g., internationalization). Comments? Suggestions? Thanks, Dima Dorfman dima@unixfreak.org Index: last.1 =================================================================== RCS file: /home/ncvs/src/usr.bin/last/last.1,v retrieving revision 1.8 diff -u -r1.8 last.1 --- last.1 2001/04/26 06:10:46 1.8 +++ last.1 2001/04/26 06:21:14 @@ -41,6 +41,7 @@ .Sh SYNOPSIS .Nm .Op Fl Ns Ar n +.Op Fl d Ar [[CC]YY][MMDD]hhmm[.SS] .Op Fl f Ar file .Op Fl h Ar host .Op Fl s @@ -49,12 +50,13 @@ .Op user ... .Sh DESCRIPTION .Nm Last -will list the sessions of specified +will either list the sessions of specified .Ar users , .Ar ttys , and .Ar hosts , -in reverse time order. +in reverse time order, +or list the users logged in at a specified date and time. Each line of output contains the user name, the tty from which the session was conducted, any hostname, the start and stop times for the session, and the duration @@ -69,6 +71,60 @@ Limits the report to .Ar n lines. +.It Fl d Ar date +Specify the snapshot date and time. +All users logged in at the snapshot date and time will +be reported. +This may be used with the +.Fl f +option to derive the results from stored wtmp files. +When this argument is provided, all other options except for +.Fl f +and +.Fl Ar n +are ignored. +The argument should be in the form +.Dq [[CC]YY][MMDD]hhmm[.SS] +where each pair of letters represents the following: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It Ar CC +The first two digits of the year (the century). +.It Ar YY +The second two digits of the year. +If +.Dq YY +is specified, but +.Dq CC +is not, a value for +.Dq YY +between 69 and 99 results in a +.Dq CC +value of 19. +Otherwise, a +.Dq CC +value of 20 is used. +.It Ar MM +Month of the year, from 1 to 12. +.It Ar DD +Day of the month, from 1 to 31. +.It Ar hh +Hour of the day, from 0 to 23. +.It Ar mm +Minute of the hour, from 0 to 59. +.It Ar SS +Second of the minute, from 0 to 61. +.El +.Pp +If the +.Dq CC +and +.Dq YY +letter pairs are not specified, the values default to the current +year. +If the +.Dq SS +letter pair is not specified, the value defaults to 0. .It Fl f Ar file .Nm Last reads the file @@ -94,8 +150,9 @@ default days, hours and minutes. .El .Pp -If -multiple arguments are given, the information which applies to any of the +If multiple arguments are given, +and a snapshot time is not specified, +the information which applies to any of the arguments is printed, e.g., .Dq Li "last root -t console" would list all of Index: last.c =================================================================== RCS file: /home/ncvs/src/usr.bin/last/last.c,v retrieving revision 1.16 diff -u -r1.16 last.c --- last.c 2001/03/21 19:08:01 1.16 +++ last.c 2001/04/26 06:21:15 @@ -62,6 +62,7 @@ #define NO 0 /* false/no */ #define YES 1 /* true/yes */ +#define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; static struct utmp buf[1024]; /* utmp read buffer */ @@ -89,11 +90,16 @@ static int sflag = 0; /* show delta in seconds */ static int width = 5; /* show seconds in delta */ static int d_first; +static time_t snaptime; /* if != 0, we will only + * report users logged in + * at this snapshot time + */ void addarg __P((int, char *)); void hostconv __P((char *)); void onintr __P((int)); char *ttyconv __P((char *)); +time_t dateconv __P((char *)); int want __P((struct utmp *)); void wtmp __P((void)); @@ -117,7 +123,8 @@ d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); maxrec = -1; - while ((ch = getopt(argc, argv, "0123456789f:h:st:w")) != -1) + snaptime = 0; + while ((ch = getopt(argc, argv, "0123456789d:f:h:st:w")) != -1) switch (ch) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': @@ -135,6 +142,9 @@ exit(0); } break; + case 'd': + snaptime = dateconv(optarg); + break; case 'f': file = optarg; break; @@ -189,6 +199,7 @@ char *crmsg; char ct[80]; struct tm *tm; + int snapfound = 0; /* found snapshot entry? */ LIST_INIT(&ttylist); @@ -220,7 +231,19 @@ currentout = -bp->ut_time; crmsg = strncmp(bp->ut_name, "shutdown", UT_NAMESIZE) ? "crash" : "shutdown"; - if (want(bp)) { + /* + * if we're in snapshop mode, we want to + * exit if this shutdown/reboot appears + * while we we are tracking the active + * range + */ + if (snaptime && snapfound) + return; + /* + * don't print shutdown/reboot entries + * unless flagged for + */ + if (!snaptime && want(bp)) { tm = localtime(&bp->ut_time); (void) strftime(ct, sizeof(ct), d_first ? "%a %e %b %R" : @@ -243,7 +266,7 @@ */ if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|') && !bp->ut_line[1]) { - if (want(bp)) { + if (want(bp) && !snaptime) { tm = localtime(&bp->ut_time); (void) strftime(ct, sizeof(ct), d_first ? "%a %e %b %R" : @@ -259,77 +282,82 @@ } continue; } - if (bp->ut_name[0] == '\0' || want(bp)) { - /* find associated tty */ - LIST_FOREACH(tt, &ttylist, list) - if (!strncmp(tt->tty, bp->ut_line, UT_LINESIZE)) - break; - - if (tt == NULL) { - /* add new one */ - tt = malloc(sizeof(struct ttytab)); - if (tt == NULL) - err(1, "malloc failure"); - tt->logout = currentout; - strncpy(tt->tty, bp->ut_line, UT_LINESIZE); - LIST_INSERT_HEAD(&ttylist, tt, list); - } - - if (bp->ut_name[0]) { - /* - * when uucp and ftp log in over a network, the entry in - * the utmp file is the name plus their process id. See - * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information. - */ - if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1)) - bp->ut_line[3] = '\0'; - else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1)) - bp->ut_line[4] = '\0'; - tm = localtime(&bp->ut_time); - (void) strftime(ct, sizeof(ct), - d_first ? "%a %e %b %R" : - "%a %b %e %R", - tm); - printf("%-*.*s %-*.*s %-*.*s %s ", - UT_NAMESIZE, UT_NAMESIZE, bp->ut_name, - UT_LINESIZE, UT_LINESIZE, bp->ut_line, - UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host, - ct); - if (!tt->logout) - puts(" still logged in"); + /* find associated tty */ + LIST_FOREACH(tt, &ttylist, list) + if (!strncmp(tt->tty, bp->ut_line, UT_LINESIZE)) + break; + + if (tt == NULL) { + /* add new one */ + tt = malloc(sizeof(struct ttytab)); + if (tt == NULL) + err(1, "malloc failure"); + tt->logout = currentout; + strncpy(tt->tty, bp->ut_line, UT_LINESIZE); + LIST_INSERT_HEAD(&ttylist, tt, list); + } + + /* + * print record if not in snapshot mode and wanted + * or in snapshot mode and in snapshot range + */ + if (bp->ut_name[0] && (want(bp) || + (bp->ut_time < snaptime && + (tt->logout > snaptime || tt->logout < 1)))) { + snapfound = 1; + /* + * when uucp and ftp log in over a network, the entry in + * the utmp file is the name plus their process id. See + * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information. + */ + if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1)) + bp->ut_line[3] = '\0'; + else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1)) + bp->ut_line[4] = '\0'; + tm = localtime(&bp->ut_time); + (void) strftime(ct, sizeof(ct), + d_first ? "%a %e %b %R" : + "%a %b %e %R", + tm); + printf("%-*.*s %-*.*s %-*.*s %s ", + UT_NAMESIZE, UT_NAMESIZE, bp->ut_name, + UT_LINESIZE, UT_LINESIZE, bp->ut_line, + UT_HOSTSIZE, UT_HOSTSIZE, bp->ut_host, + ct); + if (!tt->logout) + puts(" still logged in"); + else { + if (tt->logout < 0) { + tt->logout = -tt->logout; + printf("- %s", crmsg); + } else { - if (tt->logout < 0) { - tt->logout = -tt->logout; - printf("- %s", crmsg); - } - else { - tm = localtime(&tt->logout); - (void) strftime(ct, sizeof(ct), "%R", tm); - printf("- %s", ct); - } - delta = tt->logout - bp->ut_time; - if ( sflag ) { - printf(" (%8lu)\n", - delta); - } else { - tm = gmtime(&delta); - (void) strftime(ct, sizeof(ct), - width >= 8 ? "%T" : "%R", - tm); - if (delta < 86400) + tm = localtime(&tt->logout); + (void) strftime(ct, sizeof(ct), "%R", tm); + printf("- %s", ct); + } + delta = tt->logout - bp->ut_time; + if ( sflag ) { + printf(" (%8lu)\n", + delta); + } else { + tm = gmtime(&delta); + (void) strftime(ct, sizeof(ct), + width >= 8 ? "%T" : "%R", + tm); + if (delta < 86400) printf(" (%s)\n", ct); - else + else printf(" (%ld+%s)\n", delta / 86400, ct); - } } - LIST_REMOVE(tt, list); - free(tt); - if (maxrec != -1 && !--maxrec) - return; - } else { - tt->logout = bp->ut_time; } + LIST_REMOVE(tt, list); + free(tt); + if (maxrec != -1 && !--maxrec) + return; + } else { + tt->logout = bp->ut_time; } } } @@ -348,6 +376,9 @@ { ARG *step; + if (snaptime) + return (NO); + if (!arglist) return (YES); @@ -444,6 +475,80 @@ return (arg + 5); return (arg); } + +/* + * dateconv -- + * Convert the snapshot time in command line given in the format + * [[CC]YY]MMDDhhmm[.SS]] to a time_t. + * Derived from atime_arg1() in usr.bin/touch/touch.c + */ +time_t +dateconv(arg) + char *arg; +{ + time_t timet; + struct tm *t; + int yearset; + char *p; + + /* Start with the current time. */ + if (time(&timet) < 0) + err(1, "time"); + if ((t = localtime(&timet)) == NULL) + err(1, "localtime"); + + /* [[CC]YY]MMDDhhmm[.SS] */ + if ((p = strchr(arg, '.')) == NULL) + t->tm_sec = 0; /* Seconds defaults to 0. */ + else { + if (strlen(p + 1) != 2) + goto terr; + *p++ = '\0'; + t->tm_sec = ATOI2(p); + } + + yearset = 0; + switch (strlen(arg)) { + case 12: /* CCYYMMDDhhmm */ + t->tm_year = ATOI2(arg); + t->tm_year *= 100; + yearset = 1; + /* FALLTHOUGH */ + case 10: /* YYMMDDhhmm */ + if (yearset) { + yearset = ATOI2(arg); + t->tm_year += yearset; + } else { + yearset = ATOI2(arg); + if (yearset < 69) + t->tm_year = yearset + 2000; + else + t->tm_year = yearset + 1900; + } + t->tm_year -= 1900; /* Convert to UNIX time. */ + /* FALLTHROUGH */ + case 8: /* MMDDhhmm */ + t->tm_mon = ATOI2(arg); + --t->tm_mon; /* Convert from 01-12 to 00-11 */ + t->tm_mday = ATOI2(arg); + t->tm_hour = ATOI2(arg); + t->tm_min = ATOI2(arg); + break; + case 4: /* hhmm */ + t->tm_hour = ATOI2(arg); + t->tm_min = ATOI2(arg); + break; + default: + goto terr; + } + t->tm_isdst = -1; /* Figure out DST. */ + timet = mktime(t); + if (timet == -1) +terr: errx(1, + "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); + return timet; +} + /* * onintr -- To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-hackers" in the body of the message