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