Home | History | Annotate | Line # | Download | only in lastlogin
lastlogin.c revision 1.16
      1 /*	$NetBSD: lastlogin.c,v 1.16 2020/05/06 11:58:33 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.16 2020/05/06 11:58:33 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 struct output {
     81 	struct timeval	 o_tv;
     82 	char		 o_name[UTX_USERSIZE+1];
     83 	char		 o_line[UTX_LINESIZE+1];
     84 	char		 o_host[UTX_HOSTSIZE+1];
     85 	struct output	*next;
     86 };
     87 
     88 #define SORT_NONE	0x0000
     89 #define SORT_REVERSE	0x0001
     90 #define SORT_TIME	0x0002
     91 #define DOSORT(x)	((x) & (SORT_TIME))
     92 static	int sortlog = SORT_NONE;
     93 static	struct output *outstack = NULL;
     94 
     95 static int fixed = 0;
     96 #define FIXED_NAMELEN	UT_NAMESIZE
     97 #define FIXED_LINELEN	UT_LINESIZE
     98 /*
     99  * This makes the "fixed" output fit in 79 columns.
    100  * Using UT_HOSTSIZE (16) seems too conservative.
    101  */
    102 #define FIXED_HOSTLEN	32
    103 
    104 static int numeric = 0;
    105 static size_t namelen = 0;
    106 static size_t linelen = 0;
    107 static size_t hostlen = 0;
    108 #define SIZECOLUMNS	(!(namelen && linelen && hostlen))
    109 
    110 static	int		comparelog(const void *, const void *);
    111 static	void		output_record(struct output *);
    112 #ifdef SUPPORT_UTMP
    113 static	void		process_entry(struct passwd *, struct lastlog *);
    114 static	void		dolastlog(const char *, int, char *[]);
    115 #endif
    116 #ifdef SUPPORT_UTMPX
    117 static	void		process_entryx(struct passwd *, struct lastlogx *);
    118 static	void		dolastlogx(const char *, int, char *[]);
    119 #endif
    120 static	void		push_record(struct output *);
    121 static	void		sizecolumns(struct output *);
    122 static	void		output_stack(struct output *);
    123 static	void		sort_and_output_stack(struct output *);
    124 __dead static	void	usage(void);
    125 
    126 int
    127 main(int argc, char *argv[])
    128 {
    129 	const char *logfile =
    130 #if defined(SUPPORT_UTMPX)
    131 	    _PATH_LASTLOGX;
    132 #elif defined(SUPPORT_UTMP)
    133 	    _PATH_LASTLOG;
    134 #else
    135 	#error "either SUPPORT_UTMP or SUPPORT_UTMPX must be defined"
    136 #endif
    137 	int	ch;
    138 	size_t	len;
    139 
    140 	while ((ch = getopt(argc, argv, "f:FH:L:nN:rt")) != -1) {
    141 		switch (ch) {
    142 		case 'H':
    143 			hostlen = atoi(optarg);
    144 			break;
    145 		case 'f':
    146 			logfile = optarg;
    147 			break;
    148 		case 'F':
    149 			fixed++;
    150 			break;
    151 		case 'L':
    152 			linelen = atoi(optarg);
    153 			break;
    154 		case 'n':
    155 			numeric++;
    156 			break;
    157 		case 'N':
    158 			namelen = atoi(optarg);
    159 			break;
    160 		case 'r':
    161 			sortlog |= SORT_REVERSE;
    162 			break;
    163 		case 't':
    164 			sortlog |= SORT_TIME;
    165 			break;
    166 		default:
    167 			usage();
    168 		}
    169 	}
    170 	argc -= optind;
    171 	argv += optind;
    172 
    173 	if (fixed) {
    174 		if (!namelen)
    175 			namelen = FIXED_NAMELEN;
    176 		if (!linelen)
    177 			linelen = FIXED_LINELEN;
    178 		if (!hostlen)
    179 			hostlen = FIXED_HOSTLEN;
    180 	}
    181 
    182 	len = strlen(logfile);
    183 
    184 	setpassent(1);	/* Keep passwd file pointers open */
    185 
    186 #if defined(SUPPORT_UTMPX)
    187 	if (len > 0 && logfile[len - 1] == 'x')
    188 		dolastlogx(logfile, argc, argv);
    189 	else
    190 #endif
    191 #if defined(SUPPORT_UTMP)
    192 		dolastlog(logfile, argc, argv);
    193 #endif
    194 
    195 	setpassent(0);	/* Close passwd file pointers */
    196 
    197 	if (outstack) {
    198 		if (SIZECOLUMNS)
    199 			sizecolumns(outstack);
    200 
    201 		if (DOSORT(sortlog))
    202 			sort_and_output_stack(outstack);
    203 		else
    204 			output_stack(outstack);
    205 	}
    206 
    207 	return 0;
    208 }
    209 
    210 #ifdef SUPPORT_UTMP
    211 static void
    212 dolastlog(const char *logfile, int argc, char **argv)
    213 {
    214 	int i;
    215 	FILE *fp = fopen(logfile, "r");
    216 	struct passwd	*passwd;
    217 	struct lastlog l;
    218 
    219 	if (fp == NULL)
    220 		err(1, "%s", logfile);
    221 
    222 	/* Process usernames given on the command line. */
    223 	if (argc > 0) {
    224 		off_t offset;
    225 		for (i = 0; i < argc; i++) {
    226 			if ((passwd = getpwnam(argv[i])) == NULL) {
    227 				warnx("user '%s' not found", argv[i]);
    228 				continue;
    229 			}
    230 			/* Calculate the offset into the lastlog file. */
    231 			offset = passwd->pw_uid * sizeof(l);
    232 			if (fseeko(fp, offset, SEEK_SET)) {
    233 				warn("fseek error");
    234 				continue;
    235 			}
    236 			if (fread(&l, sizeof(l), 1, fp) != 1) {
    237 				warnx("fread error on '%s'", passwd->pw_name);
    238 				clearerr(fp);
    239 				continue;
    240 			}
    241 			process_entry(passwd, &l);
    242 		}
    243 	}
    244 	/* Read all lastlog entries, looking for active ones */
    245 	else {
    246 		for (i = 0; fread(&l, sizeof(l), 1, fp) == 1; i++) {
    247 			if (l.ll_time == 0)
    248 				continue;
    249 			if ((passwd = getpwuid(i)) == NULL) {
    250 				static struct passwd p;
    251 				static char n[32];
    252 				snprintf(n, sizeof(n), "(%d)", i);
    253 				p.pw_uid = i;
    254 				p.pw_name = n;
    255 				passwd = &p;
    256 			}
    257 			process_entry(passwd, &l);
    258 		}
    259 		if (ferror(fp))
    260 			warnx("fread error");
    261 	}
    262 
    263 	(void)fclose(fp);
    264 }
    265 
    266 static void
    267 process_entry(struct passwd *p, struct lastlog *l)
    268 {
    269 	struct output	o;
    270 
    271 	(void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name));
    272 	(void)strlcpy(o.o_line, l->ll_line, sizeof(l->ll_line));
    273 	(void)strlcpy(o.o_host, l->ll_host, sizeof(l->ll_host));
    274 	o.o_tv.tv_sec = l->ll_time;
    275 	o.o_tv.tv_usec = 0;
    276 	o.next = NULL;
    277 
    278 	/*
    279 	 * If we are dynamically sizing the columns or sorting the log,
    280 	 * we need all the entries in memory so push the current entry
    281 	 * onto a stack.  Otherwise, we can just output it.
    282 	 */
    283 	if (SIZECOLUMNS || DOSORT(sortlog))
    284 		push_record(&o);
    285 	else
    286 		output_record(&o);
    287 }
    288 #endif
    289 
    290 #ifdef SUPPORT_UTMPX
    291 static void
    292 dolastlogx(const char *logfile, int argc, char **argv)
    293 {
    294 	int i = 0;
    295 	DB *db = dbopen(logfile, O_RDONLY|O_SHLOCK, 0, DB_HASH, NULL);
    296 	DBT key, data;
    297 	struct lastlogx l;
    298 	struct passwd	*passwd;
    299 
    300 	if (db == NULL)
    301 		err(1, "%s", logfile);
    302 
    303 	if (argc > 0) {
    304 		for (i = 0; i < argc; i++) {
    305 			if ((passwd = getpwnam(argv[i])) == NULL) {
    306 				warnx("User `%s' not found", argv[i]);
    307 				continue;
    308 			}
    309 			key.data = &passwd->pw_uid;
    310 			key.size = sizeof(passwd->pw_uid);
    311 
    312 			switch ((*db->get)(db, &key, &data, 0)) {
    313 			case 0:
    314 				break;
    315 			case 1:
    316 				warnx("User `%s' not found", passwd->pw_name);
    317 				continue;
    318 			case -1:
    319 				warn("Error looking up `%s'", passwd->pw_name);
    320 				continue;
    321 			default:
    322 				abort();
    323 			}
    324 
    325 			if (data.size != sizeof(l)) {
    326 				errno = EFTYPE;
    327 				err(1, "%s", logfile);
    328 			}
    329 			(void)memcpy(&l, data.data, sizeof(l));
    330 
    331 			process_entryx(passwd, &l);
    332 		}
    333 	}
    334 	/* Read all lastlog entries, looking for active ones */
    335 	else {
    336 		switch ((*db->seq)(db, &key, &data, R_FIRST)) {
    337 		case 0:
    338 			break;
    339 		case 1:
    340 			warnx("No entries found");
    341 			(*db->close)(db);
    342 			return;
    343 		case -1:
    344 			warn("Error seeking to first entry");
    345 			(*db->close)(db);
    346 			return;
    347 		default:
    348 			abort();
    349 		}
    350 
    351 		do {
    352 			uid_t uid;
    353 
    354 			if (key.size != sizeof(uid) || data.size != sizeof(l)) {
    355 				errno = EFTYPE;
    356 				err(1, "%s", logfile);
    357 			}
    358 			(void)memcpy(&uid, key.data, sizeof(uid));
    359 
    360 			if ((passwd = getpwuid(uid)) == NULL) {
    361 				warnx("Cannot find user for uid %lu",
    362 				    (unsigned long)uid);
    363 				continue;
    364 			}
    365 			(void)memcpy(&l, data.data, sizeof(l));
    366 			process_entryx(passwd, &l);
    367 		} while ((i = (*db->seq)(db, &key, &data, R_NEXT)) == 0);
    368 
    369 		switch (i) {
    370 		case 1:
    371 			break;
    372 		case -1:
    373 			warn("Error seeking to last entry");
    374 			break;
    375 		case 0:
    376 		default:
    377 			abort();
    378 		}
    379 	}
    380 
    381 	(*db->close)(db);
    382 }
    383 
    384 static void
    385 process_entryx(struct passwd *p, struct lastlogx *l)
    386 {
    387 	struct output	o;
    388 
    389 	if (numeric > 1)
    390 		(void)snprintf(o.o_name, sizeof(o.o_name), "%d", p->pw_uid);
    391 	else
    392 		(void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name));
    393 	(void)strlcpy(o.o_line, l->ll_line, sizeof(l->ll_line));
    394 	if (!numeric)
    395 		(void)strlcpy(o.o_host, l->ll_host, sizeof(l->ll_host));
    396 	else
    397 		(void)sockaddr_snprintf(o.o_host, sizeof(o.o_host), "%a",
    398 		    (struct sockaddr *)&l->ll_ss);
    399 	o.o_tv = l->ll_tv;
    400 	o.next = NULL;
    401 
    402 	/*
    403 	 * If we are dynamically sizing the columns or sorting the log,
    404 	 * we need all the entries in memory so push the current entry
    405 	 * onto a stack.  Otherwise, we can just output it.
    406 	 */
    407 	if (SIZECOLUMNS || DOSORT(sortlog))
    408 		push_record(&o);
    409 	else
    410 		output_record(&o);
    411 }
    412 #endif
    413 
    414 static void
    415 push_record(struct output *o)
    416 {
    417 	struct output	*out;
    418 
    419 	out = malloc(sizeof(*out));
    420 	if (!out)
    421 		err(EXIT_FAILURE, "malloc failed");
    422 	(void)memcpy(out, o, sizeof(*out));
    423 	out->next = NULL;
    424 
    425 	if (outstack) {
    426 		out->next = outstack;
    427 		outstack = out;
    428 	} else {
    429 		outstack = out;
    430 	}
    431 }
    432 
    433 static void
    434 sizecolumns(struct output *stack)
    435 {
    436 	struct	output *o;
    437 	size_t	len;
    438 
    439 	if (!namelen)
    440 		for (o = stack; o; o = o->next) {
    441 			len = strlen(o->o_name);
    442 			if (namelen < len)
    443 				namelen = len;
    444 		}
    445 
    446 	if (!linelen)
    447 		for (o = stack; o; o = o->next) {
    448 			len = strlen(o->o_line);
    449 			if (linelen < len)
    450 				linelen = len;
    451 		}
    452 
    453 	if (!hostlen)
    454 		for (o = stack; o; o = o->next) {
    455 			len = strlen(o->o_host);
    456 			if (hostlen < len)
    457 				hostlen = len;
    458 		}
    459 }
    460 
    461 static void
    462 output_stack(struct output *stack)
    463 {
    464 	struct	output *o;
    465 
    466 	for (o = stack; o; o = o->next)
    467 		output_record(o);
    468 }
    469 
    470 static void
    471 sort_and_output_stack(struct output *o)
    472 {
    473 	struct	output **outs;
    474 	struct	output *tmpo;
    475 	int	num;
    476 	int	i;
    477 
    478 	/* count the number of entries to display */
    479 	for (num=0, tmpo = o; tmpo; tmpo = tmpo->next, num++)
    480 		;
    481 
    482 	outs = malloc(sizeof(*outs) * num);
    483 	if (!outs)
    484 		err(EXIT_FAILURE, "malloc failed");
    485 	for (i=0, tmpo = o; i < num; tmpo=tmpo->next, i++)
    486 		outs[i] = tmpo;
    487 
    488 	mergesort(outs, num, sizeof(*outs), comparelog);
    489 
    490 	for (i=0; i < num; i++)
    491 		output_record(outs[i]);
    492 }
    493 
    494 static int
    495 comparelog(const void *left, const void *right)
    496 {
    497 	const struct output *l = *(const struct output * const *)left;
    498 	const struct output *r = *(const struct output * const *)right;
    499 	int order = (sortlog&SORT_REVERSE)?-1:1;
    500 
    501 	if (l->o_tv.tv_sec < r->o_tv.tv_sec)
    502 		return 1 * order;
    503 	if (l->o_tv.tv_sec == r->o_tv.tv_sec)
    504 		return 0;
    505 	return -1 * order;
    506 }
    507 
    508 /* Duplicate the output of last(1) */
    509 static void
    510 output_record(struct output *o)
    511 {
    512 	time_t t = (time_t)o->o_tv.tv_sec;
    513 	printf("%-*.*s  %-*.*s  %-*.*s  %s",
    514 		(int)namelen, (int)namelen, o->o_name,
    515 		(int)linelen, (int)linelen, o->o_line,
    516 		(int)hostlen, (int)hostlen, o->o_host,
    517 		t ? ctime(&t) : "Never logged in\n");
    518 }
    519 
    520 static void
    521 usage(void)
    522 {
    523 	(void)fprintf(stderr, "Usage: %s [-nrt] [-f <filename>] "
    524 	    "[-H <hostlen>] [-L <linelen>] [-N <namelen>] [user ...]\n",
    525 	    getprogname());
    526 	exit(1);
    527 }
    528