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