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