last.c revision 1.14 1 /* $NetBSD: last.c,v 1.14 2000/06/25 13:44:43 simonb Exp $ */
2
3 /*
4 * Copyright (c) 1987, 1993, 1994
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #include <sys/cdefs.h>
37 #ifndef lint
38 __COPYRIGHT(
39 "@(#) Copyright (c) 1987, 1993, 1994\n\
40 The Regents of the University of California. All rights reserved.\n");
41 #endif /* not lint */
42
43 #ifndef lint
44 #if 0
45 static char sccsid[] = "@(#)last.c 8.2 (Berkeley) 4/2/94";
46 #endif
47 __RCSID("$NetBSD: last.c,v 1.14 2000/06/25 13:44:43 simonb Exp $");
48 #endif /* not lint */
49
50 #include <sys/param.h>
51 #include <sys/stat.h>
52
53 #include <err.h>
54 #include <fcntl.h>
55 #include <paths.h>
56 #include <signal.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <time.h>
61 #include <tzfile.h>
62 #include <unistd.h>
63 #include <utmp.h>
64
65 #define NO 0 /* false/no */
66 #define YES 1 /* true/yes */
67
68 #define TBUFLEN 30 /* length of time string buffer */
69 #define TFMT "%a %b %R" /* strftime format string */
70 #define LTFMT "%a %b %Y %T" /* strftime long format string */
71 #define TFMTS "%R" /* strftime format string - time only */
72 #define LTFMTS "%T" /* strftime long format string - " */
73
74 /* fmttime() flags */
75 #define FULLTIME 0x1 /* show year, seconds */
76 #define TIMEONLY 0x2 /* show time only, not date */
77 #define GMT 0x4 /* show time at GMT, for offsets only */
78
79 static struct utmp buf[1024]; /* utmp read buffer */
80
81 typedef struct arg {
82 char *name; /* argument */
83 #define HOST_TYPE -2
84 #define TTY_TYPE -3
85 #define USER_TYPE -4
86 int type; /* type of arg */
87 struct arg *next; /* linked list pointer */
88 } ARG;
89 ARG *arglist; /* head of linked list */
90
91 typedef struct ttytab {
92 time_t logout; /* log out time */
93 char tty[UT_LINESIZE + 1]; /* terminal name */
94 struct ttytab *next; /* linked list pointer */
95 } TTY;
96 TTY *ttylist; /* head of linked list */
97
98 static time_t currentout; /* current logout value */
99 static long maxrec; /* records to display */
100 static char *file = _PATH_WTMP; /* wtmp file */
101 static int fulltime = 0; /* Display seconds? */
102
103 int main __P((int, char *[]));
104 void addarg __P((int, char *));
105 TTY *addtty __P((char *));
106 void hostconv __P((char *));
107 void onintr __P((int));
108 char *ttyconv __P((char *));
109 int want __P((struct utmp *, int));
110 void wtmp __P((void));
111 char *fmttime __P((time_t, int));
112
113 int
114 main(argc, argv)
115 int argc;
116 char *argv[];
117 {
118 int ch;
119 char *p;
120
121 maxrec = -1;
122 while ((ch = getopt(argc, argv, "0123456789f:h:t:T")) != -1)
123 switch (ch) {
124 case '0': case '1': case '2': case '3': case '4':
125 case '5': case '6': case '7': case '8': case '9':
126 /*
127 * kludge: last was originally designed to take
128 * a number after a dash.
129 */
130 if (maxrec == -1) {
131 p = argv[optind - 1];
132 if (p[0] == '-' && p[1] == ch && !p[2])
133 maxrec = atol(++p);
134 else
135 maxrec = atol(argv[optind] + 1);
136 if (!maxrec)
137 exit(0);
138 }
139 break;
140 case 'f':
141 file = optarg;
142 break;
143 case 'h':
144 hostconv(optarg);
145 addarg(HOST_TYPE, optarg);
146 break;
147 case 't':
148 addarg(TTY_TYPE, ttyconv(optarg));
149 break;
150 case 'T':
151 fulltime = 1;
152 break;
153 case '?':
154 default:
155 (void)fprintf(stderr,
156 "usage: last [-#] [-f file] [-t tty] [-h hostname] [-T] [user ...]\n");
157 exit(1);
158 }
159
160 if (argc) {
161 setlinebuf(stdout);
162 for (argv += optind; *argv; ++argv) {
163 #define COMPATIBILITY
164 #ifdef COMPATIBILITY
165 /* code to allow "last p5" to work */
166 addarg(TTY_TYPE, ttyconv(*argv));
167 #endif
168 addarg(USER_TYPE, *argv);
169 }
170 }
171 wtmp();
172 exit(0);
173 }
174
175 /*
176 * wtmp --
177 * read through the wtmp file
178 */
179 void
180 wtmp()
181 {
182 struct utmp *bp; /* current structure */
183 TTY *T; /* tty list entry */
184 struct stat stb; /* stat of file for size */
185 time_t delta; /* time difference */
186 off_t bl;
187 int bytes, wfd;
188 char *ct, *crmsg;
189
190 crmsg = NULL;
191
192 if ((wfd = open(file, O_RDONLY, 0)) < 0 || fstat(wfd, &stb) == -1)
193 err(1, "%s", file);
194 bl = (stb.st_size + sizeof(buf) - 1) / sizeof(buf);
195
196 (void)time(&buf[0].ut_time);
197 (void)signal(SIGINT, onintr);
198 (void)signal(SIGQUIT, onintr);
199
200 while (--bl >= 0) {
201 if (lseek(wfd, bl * sizeof(buf), SEEK_SET) == -1 ||
202 (bytes = read(wfd, buf, sizeof(buf))) == -1)
203 err(1, "%s", file);
204 for (bp = &buf[bytes / sizeof(buf[0]) - 1]; bp >= buf; --bp) {
205 /*
206 * if the terminal line is '~', the machine stopped.
207 * see utmp(5) for more info.
208 */
209 if (bp->ut_line[0] == '~' && !bp->ut_line[1]) {
210 /* everybody just logged out */
211 for (T = ttylist; T; T = T->next)
212 T->logout = -bp->ut_time;
213 currentout = -bp->ut_time;
214 crmsg = strncmp(bp->ut_name, "shutdown",
215 UT_NAMESIZE) ? "crash" : "shutdown";
216 if (want(bp, NO)) {
217 ct = fmttime(bp->ut_time, fulltime);
218 printf("%-*.*s %-*.*s %-*.*s %s\n",
219 (int)UT_NAMESIZE, (int)UT_NAMESIZE,
220 bp->ut_name, (int)UT_LINESIZE,
221 (int)UT_LINESIZE, bp->ut_line,
222 (int)UT_HOSTSIZE, (int)UT_HOSTSIZE,
223 bp->ut_host, ct);
224 if (maxrec != -1 && !--maxrec)
225 return;
226 }
227 continue;
228 }
229 /*
230 * if the line is '{' or '|', date got set; see
231 * utmp(5) for more info.
232 */
233 if ((bp->ut_line[0] == '{' || bp->ut_line[0] == '|')
234 && !bp->ut_line[1]) {
235 if (want(bp, NO)) {
236 ct = fmttime(bp->ut_time, fulltime);
237 printf("%-*.*s %-*.*s %-*.*s %s\n",
238 (int)UT_NAMESIZE, (int)UT_NAMESIZE,
239 bp->ut_name,
240 (int)UT_LINESIZE, (int)UT_LINESIZE,
241 bp->ut_line,
242 (int)UT_HOSTSIZE, (int)UT_HOSTSIZE,
243 bp->ut_host,
244 ct);
245 if (maxrec && !--maxrec)
246 return;
247 }
248 continue;
249 }
250 /* find associated tty */
251 for (T = ttylist;; T = T->next) {
252 if (!T) {
253 /* add new one */
254 T = addtty(bp->ut_line);
255 break;
256 }
257 if (!strncmp(T->tty, bp->ut_line, UT_LINESIZE))
258 break;
259 }
260 if (bp->ut_name[0] && want(bp, YES)) {
261 ct = fmttime(bp->ut_time, fulltime);
262 printf("%-*.*s %-*.*s %-*.*s %s ",
263 (int)UT_NAMESIZE, (int)UT_NAMESIZE, bp->ut_name,
264 (int)UT_LINESIZE, (int)UT_LINESIZE, bp->ut_line,
265 (int)UT_HOSTSIZE, (int)UT_HOSTSIZE, bp->ut_host,
266 ct);
267 if (!T->logout)
268 puts(" still logged in");
269 else {
270 if (T->logout < 0) {
271 T->logout = -T->logout;
272 printf("- %s", crmsg);
273 }
274 else
275 printf("- %s",
276 fmttime(T->logout,
277 fulltime | TIMEONLY));
278 delta = T->logout - bp->ut_time;
279 if (delta < SECSPERDAY)
280 printf(" (%s)\n",
281 fmttime(delta,
282 fulltime | TIMEONLY | GMT));
283 else
284 printf(" (%ld+%s)\n",
285 delta / SECSPERDAY,
286 fmttime(delta,
287 fulltime | TIMEONLY | GMT));
288 }
289 if (maxrec != -1 && !--maxrec)
290 return;
291 }
292 T->logout = bp->ut_time;
293 }
294 }
295 fulltime = 1; /* show full time */
296 ct = fmttime(buf[0].ut_time, FULLTIME);
297 printf("\nwtmp begins %s\n", ct);
298 }
299
300 /*
301 * want --
302 * see if want this entry
303 */
304 int
305 want(bp, check)
306 struct utmp *bp;
307 int check;
308 {
309 ARG *step;
310
311 if (check) {
312 /*
313 * when uucp and ftp log in over a network, the entry in
314 * the utmp file is the name plus their process id. See
315 * etc/ftpd.c and usr.bin/uucp/uucpd.c for more information.
316 */
317 if (!strncmp(bp->ut_line, "ftp", sizeof("ftp") - 1))
318 bp->ut_line[3] = '\0';
319 else if (!strncmp(bp->ut_line, "uucp", sizeof("uucp") - 1))
320 bp->ut_line[4] = '\0';
321 }
322 if (!arglist)
323 return (YES);
324
325 for (step = arglist; step; step = step->next)
326 switch(step->type) {
327 case HOST_TYPE:
328 if (!strncasecmp(step->name, bp->ut_host, UT_HOSTSIZE))
329 return (YES);
330 break;
331 case TTY_TYPE:
332 if (!strncmp(step->name, bp->ut_line, UT_LINESIZE))
333 return (YES);
334 break;
335 case USER_TYPE:
336 if (!strncmp(step->name, bp->ut_name, UT_NAMESIZE))
337 return (YES);
338 break;
339 }
340 return (NO);
341 }
342
343 /*
344 * addarg --
345 * add an entry to a linked list of arguments
346 */
347 void
348 addarg(type, arg)
349 int type;
350 char *arg;
351 {
352 ARG *cur;
353
354 if (!(cur = (ARG *)malloc((u_int)sizeof(ARG))))
355 err(1, "malloc failure");
356 cur->next = arglist;
357 cur->type = type;
358 cur->name = arg;
359 arglist = cur;
360 }
361
362 /*
363 * addtty --
364 * add an entry to a linked list of ttys
365 */
366 TTY *
367 addtty(ttyname)
368 char *ttyname;
369 {
370 TTY *cur;
371
372 if (!(cur = (TTY *)malloc((u_int)sizeof(TTY))))
373 err(1, "malloc failure");
374 cur->next = ttylist;
375 cur->logout = currentout;
376 memmove(cur->tty, ttyname, UT_LINESIZE);
377 return (ttylist = cur);
378 }
379
380 /*
381 * hostconv --
382 * convert the hostname to search pattern; if the supplied host name
383 * has a domain attached that is the same as the current domain, rip
384 * off the domain suffix since that's what login(1) does.
385 */
386 void
387 hostconv(arg)
388 char *arg;
389 {
390 static int first = 1;
391 static char *hostdot, name[MAXHOSTNAMELEN + 1];
392 char *argdot;
393
394 if (!(argdot = strchr(arg, '.')))
395 return;
396 if (first) {
397 first = 0;
398 if (gethostname(name, sizeof(name)))
399 err(1, "gethostname");
400 name[sizeof(name) - 1] = '\0';
401 hostdot = strchr(name, '.');
402 }
403 if (hostdot && !strcasecmp(hostdot, argdot))
404 *argdot = '\0';
405 }
406
407 /*
408 * ttyconv --
409 * convert tty to correct name.
410 */
411 char *
412 ttyconv(arg)
413 char *arg;
414 {
415 char *mval;
416
417 /*
418 * kludge -- we assume that all tty's end with
419 * a two character suffix.
420 */
421 if (strlen(arg) == 2) {
422 /* either 6 for "ttyxx" or 8 for "console" */
423 if (!(mval = malloc((u_int)8)))
424 err(1, "malloc failure");
425 if (!strcmp(arg, "co"))
426 (void)strcpy(mval, "console");
427 else {
428 (void)strcpy(mval, "tty");
429 (void)strcpy(mval + 3, arg);
430 }
431 return (mval);
432 }
433 if (!strncmp(arg, _PATH_DEV, sizeof(_PATH_DEV) - 1))
434 return (arg + 5);
435 return (arg);
436 }
437
438 /*
439 * fmttime --
440 * return pointer to (static) formatted time string.
441 */
442 char *
443 fmttime(t, flags)
444 time_t t;
445 int flags;
446 {
447 struct tm *tm;
448 static char tbuf[TBUFLEN];
449
450 tm = (flags & GMT) ? gmtime(&t) : localtime(&t);
451 strftime(tbuf, sizeof(tbuf),
452 (flags & TIMEONLY)
453 ? (flags & FULLTIME ? LTFMTS : TFMTS)
454 : (flags & FULLTIME ? LTFMT : TFMT),
455 tm);
456 return (tbuf);
457 }
458
459 /*
460 * onintr --
461 * on interrupt, we inform the user how far we've gotten
462 */
463 void
464 onintr(signo)
465 int signo;
466 {
467
468 printf("\ninterrupted %s\n", fmttime(buf[0].ut_time, FULLTIME));
469 if (signo == SIGINT)
470 exit(1);
471 (void)fflush(stdout); /* fix required for rsh */
472 }
473