1/* $NetBSD: lastlogin.c,v 1.20 2020/05/07 12:52:40 wiz 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.20 2020/05/07 12:52:40 wiz 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#ifndef UT_NAMESIZE 61# define UT_NAMESIZE 8 62#endif 63#ifndef UT_LINESIZE 64# define UT_LINESIZE 8 65#endif 66#ifndef UT_HOSTSIZE 67# define UT_HOSTSIZE 16 68#endif 69 70#ifndef UTX_USERSIZE 71# define UTX_USERSIZE 64 72#endif 73#ifndef UTX_LINESIZE 74# define UTX_LINESIZE 64 75#endif 76#ifndef UTX_HOSTSIZE 77# define UTX_HOSTSIZE 256 78#endif 79 80/* 81 * Fields in the structure below are 1 byte longer than the maximum possible 82 * for NUL-termination. 83 */ 84struct output { 85 struct timeval o_tv; 86 char o_name[UTX_USERSIZE+1]; 87 char o_line[UTX_LINESIZE+1]; 88 char o_host[UTX_HOSTSIZE+1]; 89 struct output *next; 90}; 91 92#define SORT_NONE 0x0000 93#define SORT_REVERSE 0x0001 94#define SORT_TIME 0x0002 95#define DOSORT(x) ((x) & (SORT_TIME)) 96static int sortlog = SORT_NONE; 97static struct output *outstack = NULL; 98static struct output *outstack_p = NULL; 99 100static int fixed = 0; 101#define FIXED_NAMELEN UT_NAMESIZE 102#define FIXED_LINELEN UT_LINESIZE 103/* 104 * This makes the "fixed" output fit in 79 columns. 105 * Using UT_HOSTSIZE (16) seems too conservative. 106 */ 107#define FIXED_HOSTLEN 32 108 109static int numeric = 0; 110static size_t namelen = 0; 111static size_t linelen = 0; 112static size_t hostlen = 0; 113#define SIZECOLUMNS (!(namelen && linelen && hostlen)) 114 115static int comparelog(const void *, const void *); 116static void output_record(struct output *); 117#ifdef SUPPORT_UTMP 118static void process_entry(struct passwd *, struct lastlog *); 119static void dolastlog(const char *, int, char *[]); 120#endif 121#ifdef SUPPORT_UTMPX 122static void process_entryx(struct passwd *, struct lastlogx *); 123static void dolastlogx(const char *, int, char *[]); 124#endif 125static void append_record(struct output *); 126static void sizecolumns(struct output *); 127static void output_stack(struct output *); 128static void sort_and_output_stack(struct output *); 129__dead static void usage(void); 130 131int 132main(int argc, char *argv[]) 133{ 134 const char *logfile = 135#if defined(SUPPORT_UTMPX) 136 _PATH_LASTLOGX; 137#elif defined(SUPPORT_UTMP) 138 _PATH_LASTLOG; 139#else 140 #error "either SUPPORT_UTMP or SUPPORT_UTMPX must be defined" 141#endif 142 int ch; 143 size_t len; 144 145 while ((ch = getopt(argc, argv, "f:FH:L:nN:rt")) != -1) { 146 switch (ch) { 147 case 'H': 148 hostlen = atoi(optarg); 149 break; 150 case 'f': 151 logfile = optarg; 152 break; 153 case 'F': 154 fixed++; 155 break; 156 case 'L': 157 linelen = atoi(optarg); 158 break; 159 case 'n': 160 numeric++; 161 break; 162 case 'N': 163 namelen = atoi(optarg); 164 break; 165 case 'r': 166 sortlog |= SORT_REVERSE; 167 break; 168 case 't': 169 sortlog |= SORT_TIME; 170 break; 171 default: 172 usage(); 173 } 174 } 175 argc -= optind; 176 argv += optind; 177 178 if (fixed) { 179 if (!namelen) 180 namelen = FIXED_NAMELEN; 181 if (!linelen) 182 linelen = FIXED_LINELEN; 183 if (!hostlen) 184 hostlen = FIXED_HOSTLEN; 185 } 186 187 len = strlen(logfile); 188 189 setpassent(1); /* Keep passwd file pointers open */ 190 191#if defined(SUPPORT_UTMPX) 192 if (len > 0 && logfile[len - 1] == 'x') 193 dolastlogx(logfile, argc, argv); 194 else 195#endif 196#if defined(SUPPORT_UTMP) 197 dolastlog(logfile, argc, argv); 198#endif 199 200 setpassent(0); /* Close passwd file pointers */ 201 202 if (outstack) { 203 if (SIZECOLUMNS) 204 sizecolumns(outstack); 205 206 if (DOSORT(sortlog)) 207 sort_and_output_stack(outstack); 208 else 209 output_stack(outstack); 210 } 211 212 return 0; 213} 214 215#ifdef SUPPORT_UTMP 216static void 217dolastlog(const char *logfile, int argc, char **argv) 218{ 219 int i; 220 FILE *fp = fopen(logfile, "r"); 221 struct passwd *passwd; 222 struct lastlog l; 223 224 if (fp == NULL) 225 err(1, "%s", logfile); 226 227 /* Process usernames given on the command line. */ 228 if (argc > 0) { 229 off_t offset; 230 for (i = 0; i < argc; i++) { 231 if ((passwd = getpwnam(argv[i])) == NULL) { 232 warnx("user '%s' not found", argv[i]); 233 continue; 234 } 235 /* Calculate the offset into the lastlog file. */ 236 offset = passwd->pw_uid * sizeof(l); 237 if (fseeko(fp, offset, SEEK_SET)) { 238 warn("fseek error"); 239 continue; 240 } 241 if (fread(&l, sizeof(l), 1, fp) != 1) { 242 warnx("fread error on '%s'", passwd->pw_name); 243 clearerr(fp); 244 continue; 245 } 246 process_entry(passwd, &l); 247 } 248 } 249 /* Read all lastlog entries, looking for active ones */ 250 else { 251 for (i = 0; fread(&l, sizeof(l), 1, fp) == 1; i++) { 252 if (l.ll_time == 0) 253 continue; 254 if ((passwd = getpwuid(i)) == NULL) { 255 static struct passwd p; 256 static char n[32]; 257 snprintf(n, sizeof(n), "(%d)", i); 258 p.pw_uid = i; 259 p.pw_name = n; 260 passwd = &p; 261 } 262 process_entry(passwd, &l); 263 } 264 if (ferror(fp)) 265 warnx("fread error"); 266 } 267 268 (void)fclose(fp); 269} 270 271static void 272process_entry(struct passwd *p, struct lastlog *l) 273{ 274 struct output o; 275 276 memset(&o, 0, sizeof(o)); 277 if (numeric > 1) 278 (void)snprintf(o.o_name, sizeof(o.o_name), "%d", p->pw_uid); 279 else 280 (void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name)); 281 (void)memcpy(o.o_line, l->ll_line, sizeof(l->ll_line)); 282 (void)memcpy(o.o_host, l->ll_host, sizeof(l->ll_host)); 283 o.o_tv.tv_sec = l->ll_time; 284 o.o_tv.tv_usec = 0; 285 o.next = NULL; 286 287 /* 288 * If we are dynamically sizing the columns or sorting the log, 289 * we need all the entries in memory so push the current entry 290 * onto a stack. Otherwise, we can just output it. 291 */ 292 if (SIZECOLUMNS || DOSORT(sortlog)) 293 append_record(&o); 294 else 295 output_record(&o); 296} 297#endif 298 299#ifdef SUPPORT_UTMPX 300static void 301dolastlogx(const char *logfile, int argc, char **argv) 302{ 303 int i = 0; 304 DB *db = dbopen(logfile, O_RDONLY|O_SHLOCK, 0, DB_HASH, NULL); 305 DBT key, data; 306 struct lastlogx l; 307 struct passwd *passwd; 308 309 if (db == NULL) 310 err(1, "%s", logfile); 311 312 if (argc > 0) { 313 for (i = 0; i < argc; i++) { 314 if ((passwd = getpwnam(argv[i])) == NULL) { 315 warnx("User `%s' not found", argv[i]); 316 continue; 317 } 318 key.data = &passwd->pw_uid; 319 key.size = sizeof(passwd->pw_uid); 320 321 switch ((*db->get)(db, &key, &data, 0)) { 322 case 0: 323 break; 324 case 1: 325 warnx("User `%s' not found", passwd->pw_name); 326 continue; 327 case -1: 328 warn("Error looking up `%s'", passwd->pw_name); 329 continue; 330 default: 331 abort(); 332 } 333 334 if (data.size != sizeof(l)) { 335 errno = EFTYPE; 336 err(1, "%s", logfile); 337 } 338 (void)memcpy(&l, data.data, sizeof(l)); 339 340 process_entryx(passwd, &l); 341 } 342 } 343 /* Read all lastlog entries, looking for active ones */ 344 else { 345 switch ((*db->seq)(db, &key, &data, R_FIRST)) { 346 case 0: 347 break; 348 case 1: 349 warnx("No entries found"); 350 (*db->close)(db); 351 return; 352 case -1: 353 warn("Error seeking to first entry"); 354 (*db->close)(db); 355 return; 356 default: 357 abort(); 358 } 359 360 do { 361 uid_t uid; 362 363 if (key.size != sizeof(uid) || data.size != sizeof(l)) { 364 errno = EFTYPE; 365 err(1, "%s", logfile); 366 } 367 (void)memcpy(&uid, key.data, sizeof(uid)); 368 369 if ((passwd = getpwuid(uid)) == NULL) { 370 static struct passwd p; 371 static char n[32]; 372 snprintf(n, sizeof(n), "(%d)", i); 373 p.pw_uid = i; 374 p.pw_name = n; 375 passwd = &p; 376 } 377 (void)memcpy(&l, data.data, sizeof(l)); 378 process_entryx(passwd, &l); 379 } while ((i = (*db->seq)(db, &key, &data, R_NEXT)) == 0); 380 381 switch (i) { 382 case 1: 383 break; 384 case -1: 385 warn("Error seeking to last entry"); 386 break; 387 case 0: 388 default: 389 abort(); 390 } 391 } 392 393 (*db->close)(db); 394} 395 396static void 397process_entryx(struct passwd *p, struct lastlogx *l) 398{ 399 struct output o; 400 401 memset(&o, 0, sizeof(o)); 402 if (numeric > 1) 403 (void)snprintf(o.o_name, sizeof(o.o_name), "%d", p->pw_uid); 404 else 405 (void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name)); 406 (void)memcpy(o.o_line, l->ll_line, sizeof(l->ll_line)); 407 if (numeric) 408 (void)sockaddr_snprintf(o.o_host, sizeof(o.o_host), "%a", 409 (struct sockaddr *)&l->ll_ss); 410 else 411 (void)memcpy(o.o_host, l->ll_host, sizeof(l->ll_host)); 412 o.o_tv = l->ll_tv; 413 o.next = NULL; 414 415 /* 416 * If we are dynamically sizing the columns or sorting the log, 417 * we need all the entries in memory so push the current entry 418 * onto a stack. Otherwise, we can just output it. 419 */ 420 if (SIZECOLUMNS || DOSORT(sortlog)) 421 append_record(&o); 422 else 423 output_record(&o); 424} 425#endif 426 427static void 428append_record(struct output *o) 429{ 430 struct output *out; 431 432 out = malloc(sizeof(*out)); 433 if (!out) 434 err(EXIT_FAILURE, "malloc failed"); 435 (void)memcpy(out, o, sizeof(*out)); 436 out->next = NULL; 437 438 if (outstack_p) 439 outstack_p = outstack_p->next = out; 440 else 441 outstack = outstack_p = out; 442} 443 444static void 445sizecolumns(struct output *stack) 446{ 447 struct output *o; 448 size_t len; 449 450 if (!namelen) 451 for (o = stack; o; o = o->next) { 452 len = strlen(o->o_name); 453 if (namelen < len) 454 namelen = len; 455 } 456 457 if (!linelen) 458 for (o = stack; o; o = o->next) { 459 len = strlen(o->o_line); 460 if (linelen < len) 461 linelen = len; 462 } 463 464 if (!hostlen) 465 for (o = stack; o; o = o->next) { 466 len = strlen(o->o_host); 467 if (hostlen < len) 468 hostlen = len; 469 } 470} 471 472static void 473output_stack(struct output *stack) 474{ 475 struct output *o; 476 477 for (o = stack; o; o = o->next) 478 output_record(o); 479} 480 481static void 482sort_and_output_stack(struct output *o) 483{ 484 struct output **outs; 485 struct output *tmpo; 486 int num; 487 int i; 488 489 /* count the number of entries to display */ 490 for (num=0, tmpo = o; tmpo; tmpo = tmpo->next, num++) 491 ; 492 493 outs = malloc(sizeof(*outs) * num); 494 if (!outs) 495 err(EXIT_FAILURE, "malloc failed"); 496 for (i=0, tmpo = o; i < num; tmpo=tmpo->next, i++) 497 outs[i] = tmpo; 498 499 mergesort(outs, num, sizeof(*outs), comparelog); 500 501 for (i=0; i < num; i++) 502 output_record(outs[i]); 503} 504 505static int 506comparelog(const void *left, const void *right) 507{ 508 const struct output *l = *(const struct output * const *)left; 509 const struct output *r = *(const struct output * const *)right; 510 int order = (sortlog&SORT_REVERSE)?-1:1; 511 512 if (l->o_tv.tv_sec < r->o_tv.tv_sec) 513 return 1 * order; 514 if (l->o_tv.tv_sec == r->o_tv.tv_sec) 515 return 0; 516 return -1 * order; 517} 518 519/* Duplicate the output of last(1) */ 520static void 521output_record(struct output *o) 522{ 523 time_t t = (time_t)o->o_tv.tv_sec; 524 printf("%-*.*s %-*.*s %-*.*s %s", 525 (int)namelen, (int)namelen, o->o_name, 526 (int)linelen, (int)linelen, o->o_line, 527 (int)hostlen, (int)hostlen, o->o_host, 528 t ? ctime(&t) : "Never logged in\n"); 529} 530 531static void 532usage(void) 533{ 534 (void)fprintf(stderr, "Usage: %s [-Fnrt] [-f filename] " 535 "[-H hostsize] [-L linesize] [-N namesize] [user ...]\n", 536 getprogname()); 537 exit(1); 538} 539