Home | History | Annotate | Line # | Download | only in lastlogin
lastlogin.c revision 1.19
      1 /*	$NetBSD: lastlogin.c,v 1.19 2020/05/06 19:45:39 kim 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.19 2020/05/06 19:45:39 kim 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  */
     84 struct 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))
     96 static	int sortlog = SORT_NONE;
     97 static	struct output *outstack = NULL;
     98 static	struct output *outstack_p = NULL;
     99 
    100 static 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 
    109 static int numeric = 0;
    110 static size_t namelen = 0;
    111 static size_t linelen = 0;
    112 static size_t hostlen = 0;
    113 #define SIZECOLUMNS	(!(namelen && linelen && hostlen))
    114 
    115 static	int		comparelog(const void *, const void *);
    116 static	void		output_record(struct output *);
    117 #ifdef SUPPORT_UTMP
    118 static	void		process_entry(struct passwd *, struct lastlog *);
    119 static	void		dolastlog(const char *, int, char *[]);
    120 #endif
    121 #ifdef SUPPORT_UTMPX
    122 static	void		process_entryx(struct passwd *, struct lastlogx *);
    123 static	void		dolastlogx(const char *, int, char *[]);
    124 #endif
    125 static	void		append_record(struct output *);
    126 static	void		sizecolumns(struct output *);
    127 static	void		output_stack(struct output *);
    128 static	void		sort_and_output_stack(struct output *);
    129 __dead static	void	usage(void);
    130 
    131 int
    132 main(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
    216 static void
    217 dolastlog(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 
    271 static void
    272 process_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
    300 static void
    301 dolastlogx(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 
    396 static void
    397 process_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 
    427 static void
    428 append_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 
    444 static void
    445 sizecolumns(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 
    472 static void
    473 output_stack(struct output *stack)
    474 {
    475 	struct	output *o;
    476 
    477 	for (o = stack; o; o = o->next)
    478 		output_record(o);
    479 }
    480 
    481 static void
    482 sort_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 
    505 static int
    506 comparelog(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) */
    520 static void
    521 output_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 
    531 static void
    532 usage(void)
    533 {
    534 	(void)fprintf(stderr, "Usage: %s [-nrt] [-f <filename>] "
    535 	    "[-H <hostlen>] [-L <linelen>] [-N <namelen>] [user ...]\n",
    536 	    getprogname());
    537 	exit(1);
    538 }
    539