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 */ 84 struct 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)) 96 static int sortlog = SORT_NONE; 97 static struct output *outstack = NULL; 98 static struct output *outstack_p = NULL; 99 100 static 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 109 static int numeric = 0; 110 static size_t namelen = 0; 111 static size_t linelen = 0; 112 static size_t hostlen = 0; 113 #define SIZECOLUMNS (!(namelen && linelen && hostlen)) 114 115 static int comparelog(const void *, const void *); 116 static void output_record(struct output *); 117 #ifdef SUPPORT_UTMP 118 static void process_entry(struct passwd *, struct lastlog *); 119 static void dolastlog(const char *, int, char *[]); 120 #endif 121 #ifdef SUPPORT_UTMPX 122 static void process_entryx(struct passwd *, struct lastlogx *); 123 static void dolastlogx(const char *, int, char *[]); 124 #endif 125 static void append_record(struct output *); 126 static void sizecolumns(struct output *); 127 static void output_stack(struct output *); 128 static void sort_and_output_stack(struct output *); 129 __dead static void usage(void); 130 131 int 132 main(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 216 static void 217 dolastlog(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 271 static void 272 process_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 300 static void 301 dolastlogx(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 396 static void 397 process_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 427 static void 428 append_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 444 static void 445 sizecolumns(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 472 static void 473 output_stack(struct output *stack) 474 { 475 struct output *o; 476 477 for (o = stack; o; o = o->next) 478 output_record(o); 479 } 480 481 static void 482 sort_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 505 static int 506 comparelog(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) */ 520 static void 521 output_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 531 static void 532 usage(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