lastlogin.c revision 1.20 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