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