Home | History | Annotate | Line # | Download | only in lastlogin
lastlogin.c revision 1.13.28.1
      1 /*	$NetBSD: lastlogin.c,v 1.13.28.1 2009/05/13 19:20:27 jym 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.13.28.1 2009/05/13 19:20:27 jym 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 struct output {
     61 	struct timeval	 o_tv;
     62 	struct sockaddr_storage o_ss;
     63 	char		 o_name[64];
     64 	char		 o_line[64];
     65 	char		 o_host[256];
     66 	struct output	*next;
     67 };
     68 
     69 #define SORT_NONE	0x0000
     70 #define SORT_REVERSE	0x0001
     71 #define SORT_TIME	0x0002
     72 #define DOSORT(x)	((x) & (SORT_TIME))
     73 static	int sortlog = SORT_NONE;
     74 static	struct output *outstack = NULL;
     75 
     76 static int numeric = 0;
     77 static size_t namelen = UT_NAMESIZE;
     78 static size_t linelen = UT_LINESIZE;
     79 static size_t hostlen = UT_HOSTSIZE;
     80 
     81 	int	main(int, char **);
     82 static	int	comparelog(const void *, const void *);
     83 static	void	output(struct output *);
     84 #ifdef SUPPORT_UTMP
     85 static	void	process_entry(struct passwd *, struct lastlog *);
     86 static	void	dolastlog(const char *, int, char *[]);
     87 #endif
     88 #ifdef SUPPORT_UTMPX
     89 static	void	process_entryx(struct passwd *, struct lastlogx *);
     90 static	void	dolastlogx(const char *, int, char *[]);
     91 #endif
     92 static	void	push(struct output *);
     93 static	const char 	*gethost(struct output *);
     94 static	void	sortoutput(struct output *);
     95 static	void	usage(void);
     96 
     97 int
     98 main(argc, argv)
     99 	int argc;
    100 	char *argv[];
    101 {
    102 	const char *logfile =
    103 #if defined(SUPPORT_UTMPX)
    104 	    _PATH_LASTLOGX;
    105 #elif defined(SUPPORT_UTMP)
    106 	    _PATH_LASTLOG;
    107 #else
    108 	#error "either SUPPORT_UTMP or SUPPORT_UTMPX must be defined"
    109 #endif
    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 (outstack && 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 				static struct passwd p;
    205 				static char n[32];
    206 				snprintf(n, sizeof(n), "(%d)", i);
    207 				p.pw_uid = i;
    208 				p.pw_name = n;
    209 				passwd = &p;
    210 			}
    211 			process_entry(passwd, &l);
    212 		}
    213 		if (ferror(fp))
    214 			warnx("fread error");
    215 	}
    216 
    217 	(void)fclose(fp);
    218 }
    219 
    220 static void
    221 process_entry(struct passwd *p, struct lastlog *l)
    222 {
    223 	struct output	o;
    224 
    225 	(void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name));
    226 	(void)strlcpy(o.o_line, l->ll_line, sizeof(l->ll_line));
    227 	(void)strlcpy(o.o_host, l->ll_host, sizeof(l->ll_host));
    228 	o.o_tv.tv_sec = l->ll_time;
    229 	o.o_tv.tv_usec = 0;
    230 	(void)memset(&o.o_ss, 0, sizeof(o.o_ss));
    231 	o.next = NULL;
    232 
    233 	/*
    234 	 * If we are sorting it, we need all the entries in memory so
    235 	 * push the current entry onto a stack.  Otherwise, we can just
    236 	 * output it.
    237 	 */
    238 	if (DOSORT(sortlog))
    239 		push(&o);
    240 	else
    241 		output(&o);
    242 }
    243 #endif
    244 
    245 #ifdef SUPPORT_UTMPX
    246 static void
    247 dolastlogx(const char *logfile, int argc, char **argv)
    248 {
    249 	int i = 0;
    250 	DB *db = dbopen(logfile, O_RDONLY|O_SHLOCK, 0, DB_HASH, NULL);
    251 	DBT key, data;
    252 	struct lastlogx l;
    253 	struct passwd	*passwd;
    254 
    255 	if (db == NULL)
    256 		err(1, "%s", logfile);
    257 
    258 	if (argc > 0) {
    259 		for (i = 0; i < argc; i++) {
    260 			if ((passwd = getpwnam(argv[i])) == NULL) {
    261 				warnx("User `%s' not found", argv[i]);
    262 				continue;
    263 			}
    264 			key.data = &passwd->pw_uid;
    265 			key.size = sizeof(passwd->pw_uid);
    266 
    267 			switch ((*db->get)(db, &key, &data, 0)) {
    268 			case 0:
    269 				break;
    270 			case 1:
    271 				warnx("User `%s' not found", passwd->pw_name);
    272 				continue;
    273 			case -1:
    274 				warn("Error looking up `%s'", passwd->pw_name);
    275 				continue;
    276 			default:
    277 				abort();
    278 			}
    279 
    280 			if (data.size != sizeof(l)) {
    281 				errno = EFTYPE;
    282 				err(1, "%s", logfile);
    283 			}
    284 			(void)memcpy(&l, data.data, sizeof(l));
    285 
    286 			process_entryx(passwd, &l);
    287 		}
    288 	}
    289 	/* Read all lastlog entries, looking for active ones */
    290 	else {
    291 		switch ((*db->seq)(db, &key, &data, R_FIRST)) {
    292 		case 0:
    293 			break;
    294 		case 1:
    295 			warnx("No entries found");
    296 			(*db->close)(db);
    297 			return;
    298 		case -1:
    299 			warn("Error seeking to first entry");
    300 			(*db->close)(db);
    301 			return;
    302 		default:
    303 			abort();
    304 		}
    305 
    306 		do {
    307 			uid_t uid;
    308 
    309 			if (key.size != sizeof(uid) || data.size != sizeof(l)) {
    310 				errno = EFTYPE;
    311 				err(1, "%s", logfile);
    312 			}
    313 			(void)memcpy(&uid, key.data, sizeof(uid));
    314 
    315 			if ((passwd = getpwuid(uid)) == NULL) {
    316 				warnx("Cannot find user for uid %lu",
    317 				    (unsigned long)uid);
    318 				continue;
    319 			}
    320 			(void)memcpy(&l, data.data, sizeof(l));
    321 			process_entryx(passwd, &l);
    322 		} while ((i = (*db->seq)(db, &key, &data, R_NEXT)) == 0);
    323 
    324 		switch (i) {
    325 		case 1:
    326 			break;
    327 		case -1:
    328 			warn("Error seeking to last entry");
    329 			break;
    330 		case 0:
    331 		default:
    332 			abort();
    333 		}
    334 	}
    335 
    336 	(*db->close)(db);
    337 }
    338 
    339 static void
    340 process_entryx(struct passwd *p, struct lastlogx *l)
    341 {
    342 	struct output	o;
    343 
    344 	if (numeric > 1)
    345 		(void)snprintf(o.o_name, sizeof(o.o_name), "%d", p->pw_uid);
    346 	else
    347 		(void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name));
    348 	(void)strlcpy(o.o_line, l->ll_line, sizeof(l->ll_line));
    349 	(void)strlcpy(o.o_host, l->ll_host, sizeof(l->ll_host));
    350 	(void)memcpy(&o.o_ss, &l->ll_ss, sizeof(o.o_ss));
    351 	o.o_tv = l->ll_tv;
    352 	o.next = NULL;
    353 
    354 	/*
    355 	 * If we are sorting it, we need all the entries in memory so
    356 	 * push the current entry onto a stack.  Otherwise, we can just
    357 	 * output it.
    358 	 */
    359 	if (DOSORT(sortlog))
    360 		push(&o);
    361 	else
    362 		output(&o);
    363 }
    364 #endif
    365 
    366 static void
    367 push(struct output *o)
    368 {
    369 	struct output	*out;
    370 
    371 	out = malloc(sizeof(*out));
    372 	if (!out)
    373 		err(EXIT_FAILURE, "malloc failed");
    374 	(void)memcpy(out, o, sizeof(*out));
    375 	out->next = NULL;
    376 
    377 	if (outstack) {
    378 		out->next = outstack;
    379 		outstack = out;
    380 	} else {
    381 		outstack = out;
    382 	}
    383 }
    384 
    385 static void
    386 sortoutput(struct output *o)
    387 {
    388 	struct	output **outs;
    389 	struct	output *tmpo;
    390 	int	num;
    391 	int	i;
    392 
    393 	/* count the number of entries to display */
    394 	for (num=0, tmpo = o; tmpo; tmpo=tmpo->next, num++)
    395 		;
    396 
    397 	outs = malloc(sizeof(*outs) * num);
    398 	if (!outs)
    399 		err(EXIT_FAILURE, "malloc failed");
    400 	for (i=0, tmpo = o; i < num; tmpo=tmpo->next, i++)
    401 		outs[i] = tmpo;
    402 
    403 	mergesort(outs, num, sizeof(*outs), comparelog);
    404 
    405 	for (i=0; i < num; i++)
    406 		output(outs[i]);
    407 }
    408 
    409 static int
    410 comparelog(const void *left, const void *right)
    411 {
    412 	const struct output *l = *(const struct output * const *)left;
    413 	const struct output *r = *(const struct output * const *)right;
    414 	int order = (sortlog&SORT_REVERSE)?-1:1;
    415 
    416 	if (l->o_tv.tv_sec < r->o_tv.tv_sec)
    417 		return 1 * order;
    418 	if (l->o_tv.tv_sec == r->o_tv.tv_sec)
    419 		return 0;
    420 	return -1 * order;
    421 }
    422 
    423 static const char *
    424 gethost(struct output *o)
    425 {
    426 	if (!numeric)
    427 		return o->o_host;
    428 	else {
    429 		static char buf[512];
    430 		buf[0] = '\0';
    431 		(void)sockaddr_snprintf(buf, sizeof(buf), "%a",
    432 		    (struct sockaddr *)&o->o_ss);
    433 		return buf;
    434 	}
    435 }
    436 
    437 /* Duplicate the output of last(1) */
    438 static void
    439 output(struct output *o)
    440 {
    441 	time_t t = (time_t)o->o_tv.tv_sec;
    442 	printf("%-*.*s  %-*.*s %-*.*s   %s",
    443 		(int)namelen, (int)namelen, o->o_name,
    444 		(int)linelen, (int)linelen, o->o_line,
    445 		(int)hostlen, (int)hostlen, gethost(o),
    446 		t ? ctime(&t) : "Never logged in\n");
    447 }
    448 
    449 static void
    450 usage(void)
    451 {
    452 	(void)fprintf(stderr, "Usage: %s [-nrt] [-f <filename>] "
    453 	    "[-H <hostlen>] [-L <linelen>] [-N <namelen>] [user ...]\n",
    454 	    getprogname());
    455 	exit(1);
    456 }
    457