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