1/*	$NetBSD: lastlogin.c,v 1.20 2020/05/07 12:52:40 wiz Exp $	*/
2/*
3 * Copyright (c) 1996 John M. Vinopal
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. All advertising materials mentioning features or use of this software
15 *    must display the following acknowledgement:
16 *	This product includes software developed for the NetBSD Project
17 *	by John M. Vinopal.
18 * 4. The name of the author may not be used to endorse or promote products
19 *    derived from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 * 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#include <sys/cdefs.h>
35#ifndef lint
36__RCSID("$NetBSD: lastlogin.c,v 1.20 2020/05/07 12:52:40 wiz Exp $");
37#endif
38
39#include <sys/types.h>
40#include <err.h>
41#include <db.h>
42#include <errno.h>
43#include <pwd.h>
44#include <stdio.h>
45#include <stdlib.h>
46#include <string.h>
47#include <fcntl.h>
48#include <time.h>
49#include <arpa/inet.h>
50#include <sys/socket.h>
51#ifdef SUPPORT_UTMP
52#include <utmp.h>
53#endif
54#ifdef SUPPORT_UTMPX
55#include <utmpx.h>
56#endif
57#include <unistd.h>
58#include <util.h>
59
60#ifndef UT_NAMESIZE
61# define UT_NAMESIZE	8
62#endif
63#ifndef UT_LINESIZE
64# define UT_LINESIZE	8
65#endif
66#ifndef UT_HOSTSIZE
67# define UT_HOSTSIZE	16
68#endif
69
70#ifndef UTX_USERSIZE
71# define UTX_USERSIZE	64
72#endif
73#ifndef UTX_LINESIZE
74# define UTX_LINESIZE	64
75#endif
76#ifndef UTX_HOSTSIZE
77# define UTX_HOSTSIZE	256
78#endif
79
80/*
81 * Fields in the structure below are 1 byte longer than the maximum possible
82 * for NUL-termination.
83 */
84struct output {
85	struct timeval	 o_tv;
86	char		 o_name[UTX_USERSIZE+1];
87	char		 o_line[UTX_LINESIZE+1];
88	char		 o_host[UTX_HOSTSIZE+1];
89	struct output	*next;
90};
91
92#define SORT_NONE	0x0000
93#define SORT_REVERSE	0x0001
94#define SORT_TIME	0x0002
95#define DOSORT(x)	((x) & (SORT_TIME))
96static	int sortlog = SORT_NONE;
97static	struct output *outstack = NULL;
98static	struct output *outstack_p = NULL;
99
100static int fixed = 0;
101#define FIXED_NAMELEN	UT_NAMESIZE
102#define FIXED_LINELEN	UT_LINESIZE
103/*
104 * This makes the "fixed" output fit in 79 columns.
105 * Using UT_HOSTSIZE (16) seems too conservative.
106 */
107#define FIXED_HOSTLEN	32
108
109static int numeric = 0;
110static size_t namelen = 0;
111static size_t linelen = 0;
112static size_t hostlen = 0;
113#define SIZECOLUMNS	(!(namelen && linelen && hostlen))
114
115static	int		comparelog(const void *, const void *);
116static	void		output_record(struct output *);
117#ifdef SUPPORT_UTMP
118static	void		process_entry(struct passwd *, struct lastlog *);
119static	void		dolastlog(const char *, int, char *[]);
120#endif
121#ifdef SUPPORT_UTMPX
122static	void		process_entryx(struct passwd *, struct lastlogx *);
123static	void		dolastlogx(const char *, int, char *[]);
124#endif
125static	void		append_record(struct output *);
126static	void		sizecolumns(struct output *);
127static	void		output_stack(struct output *);
128static	void		sort_and_output_stack(struct output *);
129__dead static	void	usage(void);
130
131int
132main(int argc, char *argv[])
133{
134	const char *logfile =
135#if defined(SUPPORT_UTMPX)
136	    _PATH_LASTLOGX;
137#elif defined(SUPPORT_UTMP)
138	    _PATH_LASTLOG;
139#else
140	#error "either SUPPORT_UTMP or SUPPORT_UTMPX must be defined"
141#endif
142	int	ch;
143	size_t	len;
144
145	while ((ch = getopt(argc, argv, "f:FH:L:nN:rt")) != -1) {
146		switch (ch) {
147		case 'H':
148			hostlen = atoi(optarg);
149			break;
150		case 'f':
151			logfile = optarg;
152			break;
153		case 'F':
154			fixed++;
155			break;
156		case 'L':
157			linelen = atoi(optarg);
158			break;
159		case 'n':
160			numeric++;
161			break;
162		case 'N':
163			namelen = atoi(optarg);
164			break;
165		case 'r':
166			sortlog |= SORT_REVERSE;
167			break;
168		case 't':
169			sortlog |= SORT_TIME;
170			break;
171		default:
172			usage();
173		}
174	}
175	argc -= optind;
176	argv += optind;
177
178	if (fixed) {
179		if (!namelen)
180			namelen = FIXED_NAMELEN;
181		if (!linelen)
182			linelen = FIXED_LINELEN;
183		if (!hostlen)
184			hostlen = FIXED_HOSTLEN;
185	}
186
187	len = strlen(logfile);
188
189	setpassent(1);	/* Keep passwd file pointers open */
190
191#if defined(SUPPORT_UTMPX)
192	if (len > 0 && logfile[len - 1] == 'x')
193		dolastlogx(logfile, argc, argv);
194	else
195#endif
196#if defined(SUPPORT_UTMP)
197		dolastlog(logfile, argc, argv);
198#endif
199
200	setpassent(0);	/* Close passwd file pointers */
201
202	if (outstack) {
203		if (SIZECOLUMNS)
204			sizecolumns(outstack);
205
206		if (DOSORT(sortlog))
207			sort_and_output_stack(outstack);
208		else
209			output_stack(outstack);
210	}
211
212	return 0;
213}
214
215#ifdef SUPPORT_UTMP
216static void
217dolastlog(const char *logfile, int argc, char **argv)
218{
219	int i;
220	FILE *fp = fopen(logfile, "r");
221	struct passwd	*passwd;
222	struct lastlog l;
223
224	if (fp == NULL)
225		err(1, "%s", logfile);
226
227	/* Process usernames given on the command line. */
228	if (argc > 0) {
229		off_t offset;
230		for (i = 0; i < argc; i++) {
231			if ((passwd = getpwnam(argv[i])) == NULL) {
232				warnx("user '%s' not found", argv[i]);
233				continue;
234			}
235			/* Calculate the offset into the lastlog file. */
236			offset = passwd->pw_uid * sizeof(l);
237			if (fseeko(fp, offset, SEEK_SET)) {
238				warn("fseek error");
239				continue;
240			}
241			if (fread(&l, sizeof(l), 1, fp) != 1) {
242				warnx("fread error on '%s'", passwd->pw_name);
243				clearerr(fp);
244				continue;
245			}
246			process_entry(passwd, &l);
247		}
248	}
249	/* Read all lastlog entries, looking for active ones */
250	else {
251		for (i = 0; fread(&l, sizeof(l), 1, fp) == 1; i++) {
252			if (l.ll_time == 0)
253				continue;
254			if ((passwd = getpwuid(i)) == NULL) {
255				static struct passwd p;
256				static char n[32];
257				snprintf(n, sizeof(n), "(%d)", i);
258				p.pw_uid = i;
259				p.pw_name = n;
260				passwd = &p;
261			}
262			process_entry(passwd, &l);
263		}
264		if (ferror(fp))
265			warnx("fread error");
266	}
267
268	(void)fclose(fp);
269}
270
271static void
272process_entry(struct passwd *p, struct lastlog *l)
273{
274	struct output	o;
275
276	memset(&o, 0, sizeof(o));
277	if (numeric > 1)
278		(void)snprintf(o.o_name, sizeof(o.o_name), "%d", p->pw_uid);
279	else
280		(void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name));
281	(void)memcpy(o.o_line, l->ll_line, sizeof(l->ll_line));
282	(void)memcpy(o.o_host, l->ll_host, sizeof(l->ll_host));
283	o.o_tv.tv_sec = l->ll_time;
284	o.o_tv.tv_usec = 0;
285	o.next = NULL;
286
287	/*
288	 * If we are dynamically sizing the columns or sorting the log,
289	 * we need all the entries in memory so push the current entry
290	 * onto a stack.  Otherwise, we can just output it.
291	 */
292	if (SIZECOLUMNS || DOSORT(sortlog))
293		append_record(&o);
294	else
295		output_record(&o);
296}
297#endif
298
299#ifdef SUPPORT_UTMPX
300static void
301dolastlogx(const char *logfile, int argc, char **argv)
302{
303	int i = 0;
304	DB *db = dbopen(logfile, O_RDONLY|O_SHLOCK, 0, DB_HASH, NULL);
305	DBT key, data;
306	struct lastlogx l;
307	struct passwd	*passwd;
308
309	if (db == NULL)
310		err(1, "%s", logfile);
311
312	if (argc > 0) {
313		for (i = 0; i < argc; i++) {
314			if ((passwd = getpwnam(argv[i])) == NULL) {
315				warnx("User `%s' not found", argv[i]);
316				continue;
317			}
318			key.data = &passwd->pw_uid;
319			key.size = sizeof(passwd->pw_uid);
320
321			switch ((*db->get)(db, &key, &data, 0)) {
322			case 0:
323				break;
324			case 1:
325				warnx("User `%s' not found", passwd->pw_name);
326				continue;
327			case -1:
328				warn("Error looking up `%s'", passwd->pw_name);
329				continue;
330			default:
331				abort();
332			}
333
334			if (data.size != sizeof(l)) {
335				errno = EFTYPE;
336				err(1, "%s", logfile);
337			}
338			(void)memcpy(&l, data.data, sizeof(l));
339
340			process_entryx(passwd, &l);
341		}
342	}
343	/* Read all lastlog entries, looking for active ones */
344	else {
345		switch ((*db->seq)(db, &key, &data, R_FIRST)) {
346		case 0:
347			break;
348		case 1:
349			warnx("No entries found");
350			(*db->close)(db);
351			return;
352		case -1:
353			warn("Error seeking to first entry");
354			(*db->close)(db);
355			return;
356		default:
357			abort();
358		}
359
360		do {
361			uid_t uid;
362
363			if (key.size != sizeof(uid) || data.size != sizeof(l)) {
364				errno = EFTYPE;
365				err(1, "%s", logfile);
366			}
367			(void)memcpy(&uid, key.data, sizeof(uid));
368
369			if ((passwd = getpwuid(uid)) == NULL) {
370				static struct passwd p;
371				static char n[32];
372				snprintf(n, sizeof(n), "(%d)", i);
373				p.pw_uid = i;
374				p.pw_name = n;
375				passwd = &p;
376			}
377			(void)memcpy(&l, data.data, sizeof(l));
378			process_entryx(passwd, &l);
379		} while ((i = (*db->seq)(db, &key, &data, R_NEXT)) == 0);
380
381		switch (i) {
382		case 1:
383			break;
384		case -1:
385			warn("Error seeking to last entry");
386			break;
387		case 0:
388		default:
389			abort();
390		}
391	}
392
393	(*db->close)(db);
394}
395
396static void
397process_entryx(struct passwd *p, struct lastlogx *l)
398{
399	struct output	o;
400
401	memset(&o, 0, sizeof(o));
402	if (numeric > 1)
403		(void)snprintf(o.o_name, sizeof(o.o_name), "%d", p->pw_uid);
404	else
405		(void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name));
406	(void)memcpy(o.o_line, l->ll_line, sizeof(l->ll_line));
407	if (numeric)
408		(void)sockaddr_snprintf(o.o_host, sizeof(o.o_host), "%a",
409		    (struct sockaddr *)&l->ll_ss);
410	else
411		(void)memcpy(o.o_host, l->ll_host, sizeof(l->ll_host));
412	o.o_tv = l->ll_tv;
413	o.next = NULL;
414
415	/*
416	 * If we are dynamically sizing the columns or sorting the log,
417	 * we need all the entries in memory so push the current entry
418	 * onto a stack.  Otherwise, we can just output it.
419	 */
420	if (SIZECOLUMNS || DOSORT(sortlog))
421		append_record(&o);
422	else
423		output_record(&o);
424}
425#endif
426
427static void
428append_record(struct output *o)
429{
430	struct output	*out;
431
432	out = malloc(sizeof(*out));
433	if (!out)
434		err(EXIT_FAILURE, "malloc failed");
435	(void)memcpy(out, o, sizeof(*out));
436	out->next = NULL;
437
438	if (outstack_p)
439		outstack_p = outstack_p->next = out;
440	else
441		outstack = outstack_p = out;
442}
443
444static void
445sizecolumns(struct output *stack)
446{
447	struct	output *o;
448	size_t	len;
449
450	if (!namelen)
451		for (o = stack; o; o = o->next) {
452			len = strlen(o->o_name);
453			if (namelen < len)
454				namelen = len;
455		}
456
457	if (!linelen)
458		for (o = stack; o; o = o->next) {
459			len = strlen(o->o_line);
460			if (linelen < len)
461				linelen = len;
462		}
463
464	if (!hostlen)
465		for (o = stack; o; o = o->next) {
466			len = strlen(o->o_host);
467			if (hostlen < len)
468				hostlen = len;
469		}
470}
471
472static void
473output_stack(struct output *stack)
474{
475	struct	output *o;
476
477	for (o = stack; o; o = o->next)
478		output_record(o);
479}
480
481static void
482sort_and_output_stack(struct output *o)
483{
484	struct	output **outs;
485	struct	output *tmpo;
486	int	num;
487	int	i;
488
489	/* count the number of entries to display */
490	for (num=0, tmpo = o; tmpo; tmpo = tmpo->next, num++)
491		;
492
493	outs = malloc(sizeof(*outs) * num);
494	if (!outs)
495		err(EXIT_FAILURE, "malloc failed");
496	for (i=0, tmpo = o; i < num; tmpo=tmpo->next, i++)
497		outs[i] = tmpo;
498
499	mergesort(outs, num, sizeof(*outs), comparelog);
500
501	for (i=0; i < num; i++)
502		output_record(outs[i]);
503}
504
505static int
506comparelog(const void *left, const void *right)
507{
508	const struct output *l = *(const struct output * const *)left;
509	const struct output *r = *(const struct output * const *)right;
510	int order = (sortlog&SORT_REVERSE)?-1:1;
511
512	if (l->o_tv.tv_sec < r->o_tv.tv_sec)
513		return 1 * order;
514	if (l->o_tv.tv_sec == r->o_tv.tv_sec)
515		return 0;
516	return -1 * order;
517}
518
519/* Duplicate the output of last(1) */
520static void
521output_record(struct output *o)
522{
523	time_t t = (time_t)o->o_tv.tv_sec;
524	printf("%-*.*s  %-*.*s  %-*.*s  %s",
525		(int)namelen, (int)namelen, o->o_name,
526		(int)linelen, (int)linelen, o->o_line,
527		(int)hostlen, (int)hostlen, o->o_host,
528		t ? ctime(&t) : "Never logged in\n");
529}
530
531static void
532usage(void)
533{
534	(void)fprintf(stderr, "Usage: %s [-Fnrt] [-f filename] "
535	    "[-H hostsize] [-L linesize] [-N namesize] [user ...]\n",
536	    getprogname());
537	exit(1);
538}
539