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