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