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