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