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