lastlogin.c revision 1.9 1 1.9 christos /* $NetBSD: lastlogin.c,v 1.9 2004/11/11 20:17:36 christos Exp $ */
2 1.1 phil /*
3 1.1 phil * Copyright (c) 1996 John M. Vinopal
4 1.1 phil * All rights reserved.
5 1.1 phil *
6 1.1 phil * Redistribution and use in source and binary forms, with or without
7 1.1 phil * modification, are permitted provided that the following conditions
8 1.1 phil * are met:
9 1.1 phil * 1. Redistributions of source code must retain the above copyright
10 1.1 phil * notice, this list of conditions and the following disclaimer.
11 1.1 phil * 2. Redistributions in binary form must reproduce the above copyright
12 1.1 phil * notice, this list of conditions and the following disclaimer in the
13 1.1 phil * documentation and/or other materials provided with the distribution.
14 1.1 phil * 3. All advertising materials mentioning features or use of this software
15 1.1 phil * must display the following acknowledgement:
16 1.1 phil * This product includes software developed for the NetBSD Project
17 1.1 phil * by John M. Vinopal.
18 1.1 phil * 4. The name of the author may not be used to endorse or promote products
19 1.1 phil * derived from this software without specific prior written permission.
20 1.1 phil *
21 1.1 phil * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 1.1 phil * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 1.1 phil * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 1.1 phil * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 1.1 phil * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 1.1 phil * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 1.1 phil * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 1.1 phil * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 1.1 phil * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 1.1 phil * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 1.1 phil * SUCH DAMAGE.
32 1.1 phil */
33 1.1 phil
34 1.3 lukem #include <sys/cdefs.h>
35 1.3 lukem #ifndef lint
36 1.9 christos __RCSID("$NetBSD: lastlogin.c,v 1.9 2004/11/11 20:17:36 christos Exp $");
37 1.3 lukem #endif
38 1.3 lukem
39 1.1 phil #include <sys/types.h>
40 1.3 lukem #include <err.h>
41 1.9 christos #include <db.h>
42 1.1 phil #include <errno.h>
43 1.1 phil #include <pwd.h>
44 1.1 phil #include <stdio.h>
45 1.5 matt #include <stdlib.h>
46 1.7 elric #include <string.h>
47 1.9 christos #include <fcntl.h>
48 1.3 lukem #include <time.h>
49 1.9 christos #include <arpa/inet.h>
50 1.9 christos #include <sys/socket.h>
51 1.9 christos #ifdef SUPPORT_UTMP
52 1.3 lukem #include <utmp.h>
53 1.9 christos #endif
54 1.9 christos #ifdef SUPPORT_UTMPX
55 1.9 christos #include <utmpx.h>
56 1.9 christos #endif
57 1.4 perry #include <unistd.h>
58 1.1 phil
59 1.7 elric struct output {
60 1.9 christos struct timeval o_tv;
61 1.9 christos struct sockaddr_storage o_ss;
62 1.9 christos char o_name[64];
63 1.9 christos char o_line[64];
64 1.9 christos char o_host[256];
65 1.7 elric struct output *next;
66 1.7 elric };
67 1.7 elric
68 1.9 christos static char *logfile =
69 1.9 christos #if defined(SUPPORT_UTMPX)
70 1.9 christos _PATH_LASTLOGX;
71 1.9 christos #elif defined(SUPPORT_UTMP)
72 1.9 christos _PATH_LASTLOG;
73 1.9 christos #else
74 1.9 christos #error "either SUPPORT_UTMP or SUPPORT_UTMPX must be defined"
75 1.9 christos #endif
76 1.1 phil
77 1.7 elric #define SORT_NONE 0x0000
78 1.7 elric #define SORT_REVERSE 0x0001
79 1.7 elric #define SORT_TIME 0x0002
80 1.7 elric #define DOSORT(x) ((x) & (SORT_TIME))
81 1.7 elric static int sortlog = SORT_NONE;
82 1.7 elric static struct output *outstack = NULL;
83 1.7 elric
84 1.9 christos static int numeric = 0;
85 1.9 christos static size_t namelen = UT_NAMESIZE;
86 1.9 christos static size_t linelen = UT_LINESIZE;
87 1.9 christos static size_t hostlen = UT_HOSTSIZE;
88 1.9 christos
89 1.9 christos int main(int, char **);
90 1.9 christos static int comparelog(const void *, const void *);
91 1.9 christos static void output(struct output *);
92 1.9 christos #ifdef SUPPORT_UTMP
93 1.9 christos static void process_entry(struct passwd *, struct lastlog *);
94 1.9 christos static void dolastlog(const char *, int, char *[]);
95 1.9 christos #endif
96 1.9 christos #ifdef SUPPORT_UTMPX
97 1.9 christos static void process_entryx(struct passwd *, struct lastlogx *);
98 1.9 christos static void dolastlogx(const char *, int, char *[]);
99 1.9 christos #endif
100 1.9 christos static void push(struct output *);
101 1.9 christos static const char *gethost(struct output *);
102 1.9 christos static void sortoutput(struct output *);
103 1.9 christos static void usage(void);
104 1.1 phil
105 1.1 phil int
106 1.1 phil main(argc, argv)
107 1.1 phil int argc;
108 1.1 phil char *argv[];
109 1.1 phil {
110 1.9 christos int ch;
111 1.9 christos size_t len;
112 1.1 phil
113 1.9 christos while ((ch = getopt(argc, argv, "f:H:L:nN:rt")) != -1) {
114 1.7 elric switch (ch) {
115 1.9 christos case 'H':
116 1.9 christos hostlen = atoi(optarg);
117 1.9 christos break;
118 1.9 christos case 'f':
119 1.9 christos logfile = optarg;
120 1.9 christos break;
121 1.9 christos case 'L':
122 1.9 christos linelen = atoi(optarg);
123 1.9 christos break;
124 1.9 christos case 'n':
125 1.9 christos numeric++;
126 1.9 christos break;
127 1.9 christos case 'N':
128 1.9 christos namelen = atoi(optarg);
129 1.9 christos break;
130 1.7 elric case 'r':
131 1.7 elric sortlog |= SORT_REVERSE;
132 1.7 elric break;
133 1.7 elric case 't':
134 1.7 elric sortlog |= SORT_TIME;
135 1.7 elric break;
136 1.7 elric default:
137 1.7 elric usage();
138 1.7 elric }
139 1.1 phil }
140 1.7 elric argc -= optind;
141 1.7 elric argv += optind;
142 1.1 phil
143 1.9 christos len = strlen(logfile);
144 1.9 christos
145 1.9 christos setpassent(1); /* Keep passwd file pointers open */
146 1.9 christos
147 1.9 christos #if defined(SUPPORT_UTMPX)
148 1.9 christos if (len > 0 && logfile[len - 1] == 'x')
149 1.9 christos dolastlogx(logfile, argc, argv);
150 1.9 christos else
151 1.9 christos #endif
152 1.9 christos #if defined(SUPPORT_UTMP)
153 1.9 christos dolastlog(logfile, argc, argv);
154 1.9 christos #endif
155 1.9 christos
156 1.9 christos setpassent(0); /* Close passwd file pointers */
157 1.9 christos
158 1.9 christos if (DOSORT(sortlog))
159 1.9 christos sortoutput(outstack);
160 1.9 christos
161 1.9 christos return 0;
162 1.9 christos }
163 1.9 christos
164 1.9 christos #ifdef SUPPORT_UTMP
165 1.9 christos static void
166 1.9 christos dolastlog(const char *logfile, int argc, char **argv)
167 1.9 christos {
168 1.9 christos int i;
169 1.9 christos FILE *fp = fopen(logfile, "r");
170 1.9 christos struct passwd *passwd;
171 1.9 christos struct lastlog l;
172 1.9 christos
173 1.1 phil if (fp == NULL)
174 1.1 phil err(1, "%s", logfile);
175 1.1 phil
176 1.1 phil /* Process usernames given on the command line. */
177 1.8 wulf if (argc > 0) {
178 1.9 christos off_t offset;
179 1.8 wulf for (i = 0; i < argc; i++) {
180 1.1 phil if ((passwd = getpwnam(argv[i])) == NULL) {
181 1.1 phil warnx("user '%s' not found", argv[i]);
182 1.1 phil continue;
183 1.1 phil }
184 1.1 phil /* Calculate the offset into the lastlog file. */
185 1.9 christos offset = passwd->pw_uid * sizeof(l);
186 1.9 christos if (fseeko(fp, offset, SEEK_SET)) {
187 1.1 phil warn("fseek error");
188 1.1 phil continue;
189 1.1 phil }
190 1.9 christos if (fread(&l, sizeof(l), 1, fp) != 1) {
191 1.1 phil warnx("fread error on '%s'", passwd->pw_name);
192 1.1 phil clearerr(fp);
193 1.1 phil continue;
194 1.1 phil }
195 1.9 christos process_entry(passwd, &l);
196 1.1 phil }
197 1.1 phil }
198 1.1 phil /* Read all lastlog entries, looking for active ones */
199 1.1 phil else {
200 1.9 christos for (i = 0; fread(&l, sizeof(l), 1, fp) == 1; i++) {
201 1.9 christos if (l.ll_time == 0)
202 1.2 christos continue;
203 1.2 christos if ((passwd = getpwuid(i)) != NULL)
204 1.9 christos process_entry(passwd, &l);
205 1.1 phil }
206 1.1 phil if (ferror(fp))
207 1.1 phil warnx("fread error");
208 1.1 phil }
209 1.1 phil
210 1.9 christos (void)fclose(fp);
211 1.9 christos }
212 1.9 christos
213 1.9 christos static void
214 1.9 christos process_entry(struct passwd *p, struct lastlog *l)
215 1.9 christos {
216 1.9 christos struct output o;
217 1.9 christos
218 1.9 christos (void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name));
219 1.9 christos (void)strlcpy(o.o_line, l->ll_line, sizeof(l->ll_line));
220 1.9 christos (void)strlcpy(o.o_host, l->ll_host, sizeof(l->ll_host));
221 1.9 christos o.o_tv.tv_sec = l->ll_time;
222 1.9 christos o.o_tv.tv_usec = 0;
223 1.9 christos (void)memset(&o.o_ss, 0, sizeof(o.o_ss));
224 1.9 christos o.next = NULL;
225 1.1 phil
226 1.9 christos /*
227 1.9 christos * If we are sorting it, we need all the entries in memory so
228 1.9 christos * push the current entry onto a stack. Otherwise, we can just
229 1.9 christos * output it.
230 1.9 christos */
231 1.7 elric if (DOSORT(sortlog))
232 1.9 christos push(&o);
233 1.9 christos else
234 1.9 christos output(&o);
235 1.9 christos }
236 1.9 christos #endif
237 1.9 christos
238 1.9 christos #ifdef SUPPORT_UTMPX
239 1.9 christos static void
240 1.9 christos dolastlogx(const char *logfile, int argc, char **argv)
241 1.9 christos {
242 1.9 christos int i = 0;
243 1.9 christos DB *db = dbopen(logfile, O_RDONLY|O_SHLOCK, 0, DB_HASH, NULL);
244 1.9 christos DBT key, data;
245 1.9 christos struct lastlogx l;
246 1.9 christos struct passwd *passwd;
247 1.9 christos
248 1.9 christos if (db == NULL)
249 1.9 christos err(1, "%s", logfile);
250 1.9 christos
251 1.9 christos if (argc > 0) {
252 1.9 christos for (i = 0; i < argc; i++) {
253 1.9 christos if ((passwd = getpwnam(argv[i])) == NULL) {
254 1.9 christos warnx("User `%s' not found", argv[i]);
255 1.9 christos continue;
256 1.9 christos }
257 1.9 christos key.data = &passwd->pw_uid;
258 1.9 christos key.size = sizeof(passwd->pw_uid);
259 1.9 christos
260 1.9 christos switch ((*db->get)(db, &key, &data, 0)) {
261 1.9 christos case 0:
262 1.9 christos break;
263 1.9 christos case 1:
264 1.9 christos warnx("User `%s' not found", passwd->pw_name);
265 1.9 christos continue;
266 1.9 christos case -1:
267 1.9 christos warn("Error looking up `%s'", passwd->pw_name);
268 1.9 christos continue;
269 1.9 christos default:
270 1.9 christos abort();
271 1.9 christos }
272 1.9 christos
273 1.9 christos if (data.size != sizeof(l)) {
274 1.9 christos errno = EFTYPE;
275 1.9 christos err(1, "%s", logfile);
276 1.9 christos }
277 1.9 christos (void)memcpy(&l, data.data, sizeof(l));
278 1.9 christos
279 1.9 christos process_entryx(passwd, &l);
280 1.9 christos }
281 1.9 christos }
282 1.9 christos /* Read all lastlog entries, looking for active ones */
283 1.9 christos else {
284 1.9 christos switch ((*db->seq)(db, &key, &data, R_FIRST)) {
285 1.9 christos case 0:
286 1.9 christos break;
287 1.9 christos case 1:
288 1.9 christos warnx("No entries found");
289 1.9 christos (*db->close)(db);
290 1.9 christos return;
291 1.9 christos case -1:
292 1.9 christos warn("Error seeking to first entry");
293 1.9 christos (*db->close)(db);
294 1.9 christos return;
295 1.9 christos default:
296 1.9 christos abort();
297 1.9 christos }
298 1.9 christos
299 1.9 christos do {
300 1.9 christos uid_t uid;
301 1.9 christos
302 1.9 christos if (key.size != sizeof(uid) || data.size != sizeof(l)) {
303 1.9 christos errno = EFTYPE;
304 1.9 christos err(1, "%s", logfile);
305 1.9 christos }
306 1.9 christos (void)memcpy(&uid, key.data, sizeof(uid));
307 1.9 christos
308 1.9 christos if ((passwd = getpwuid(uid)) == NULL) {
309 1.9 christos warnx("Cannot find user for uid %lu",
310 1.9 christos (unsigned long)uid);
311 1.9 christos continue;
312 1.9 christos }
313 1.9 christos (void)memcpy(&l, data.data, sizeof(l));
314 1.9 christos process_entryx(passwd, &l);
315 1.9 christos } while ((i = (*db->seq)(db, &key, &data, R_NEXT)) == 0);
316 1.9 christos
317 1.9 christos switch (i) {
318 1.9 christos case 1:
319 1.9 christos break;
320 1.9 christos case -1:
321 1.9 christos warn("Error seeking to last entry");
322 1.9 christos break;
323 1.9 christos case 0:
324 1.9 christos default:
325 1.9 christos abort();
326 1.9 christos }
327 1.9 christos }
328 1.7 elric
329 1.9 christos (*db->close)(db);
330 1.1 phil }
331 1.1 phil
332 1.7 elric static void
333 1.9 christos process_entryx(struct passwd *p, struct lastlogx *l)
334 1.7 elric {
335 1.7 elric struct output o;
336 1.7 elric
337 1.9 christos (void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name));
338 1.9 christos (void)strlcpy(o.o_line, l->ll_line, sizeof(l->ll_line));
339 1.9 christos (void)strlcpy(o.o_host, l->ll_host, sizeof(l->ll_host));
340 1.9 christos (void)memcpy(&o.o_ss, &l->ll_ss, sizeof(o.o_ss));
341 1.9 christos o.o_tv = l->ll_tv;
342 1.7 elric o.next = NULL;
343 1.7 elric
344 1.7 elric /*
345 1.7 elric * If we are sorting it, we need all the entries in memory so
346 1.7 elric * push the current entry onto a stack. Otherwise, we can just
347 1.7 elric * output it.
348 1.7 elric */
349 1.7 elric if (DOSORT(sortlog))
350 1.7 elric push(&o);
351 1.7 elric else
352 1.7 elric output(&o);
353 1.7 elric }
354 1.9 christos #endif
355 1.7 elric
356 1.7 elric static void
357 1.7 elric push(struct output *o)
358 1.7 elric {
359 1.7 elric struct output *out;
360 1.7 elric
361 1.7 elric out = malloc(sizeof(*out));
362 1.7 elric if (!out)
363 1.7 elric err(EXIT_FAILURE, "malloc failed");
364 1.9 christos (void)memcpy(out, o, sizeof(*out));
365 1.7 elric out->next = NULL;
366 1.7 elric
367 1.7 elric if (outstack) {
368 1.7 elric out->next = outstack;
369 1.7 elric outstack = out;
370 1.7 elric } else {
371 1.7 elric outstack = out;
372 1.7 elric }
373 1.7 elric }
374 1.7 elric
375 1.7 elric static void
376 1.7 elric sortoutput(struct output *o)
377 1.7 elric {
378 1.7 elric struct output **outs;
379 1.7 elric struct output *tmpo;
380 1.7 elric int num;
381 1.7 elric int i;
382 1.7 elric
383 1.7 elric /* count the number of entries to display */
384 1.7 elric for (num=0, tmpo = o; tmpo; tmpo=tmpo->next, num++)
385 1.7 elric ;
386 1.7 elric
387 1.7 elric outs = malloc(sizeof(*outs) * num);
388 1.7 elric if (!outs)
389 1.7 elric err(EXIT_FAILURE, "malloc failed");
390 1.7 elric for (i=0, tmpo = o; i < num; tmpo=tmpo->next, i++)
391 1.7 elric outs[i] = tmpo;
392 1.7 elric
393 1.7 elric mergesort(outs, num, sizeof(*outs), comparelog);
394 1.7 elric
395 1.7 elric for (i=0; i < num; i++)
396 1.7 elric output(outs[i]);
397 1.7 elric }
398 1.7 elric
399 1.7 elric static int
400 1.7 elric comparelog(const void *left, const void *right)
401 1.7 elric {
402 1.7 elric struct output *l = *(struct output **)left;
403 1.7 elric struct output *r = *(struct output **)right;
404 1.7 elric int order = (sortlog&SORT_REVERSE)?-1:1;
405 1.7 elric
406 1.9 christos if (l->o_tv.tv_sec < r->o_tv.tv_sec)
407 1.7 elric return 1 * order;
408 1.9 christos if (l->o_tv.tv_sec == r->o_tv.tv_sec)
409 1.7 elric return 0;
410 1.7 elric return -1 * order;
411 1.7 elric }
412 1.7 elric
413 1.9 christos static const char *
414 1.9 christos gethost(struct output *o)
415 1.9 christos {
416 1.9 christos if (!numeric)
417 1.9 christos return o->o_host;
418 1.9 christos else {
419 1.9 christos static char buf[512];
420 1.9 christos const char *p;
421 1.9 christos struct sockaddr_storage *ss = &o->o_ss;
422 1.9 christos void *a;
423 1.9 christos switch (ss->ss_family) {
424 1.9 christos default:
425 1.9 christos (void)snprintf(buf, sizeof(buf), "%d: unknown family",
426 1.9 christos ss->ss_family);
427 1.9 christos return buf;
428 1.9 christos case 0: /* reboot etc. entries */
429 1.9 christos return "";
430 1.9 christos case AF_INET:
431 1.9 christos a = &((struct sockaddr_in *)(void *)ss)->sin_addr;
432 1.9 christos break;
433 1.9 christos case AF_INET6:
434 1.9 christos a = &((struct sockaddr_in6 *)(void *)ss)->sin6_addr;
435 1.9 christos break;
436 1.9 christos }
437 1.9 christos if ((p = inet_ntop(ss->ss_family, a, buf, ss->ss_len)) != NULL)
438 1.9 christos return p;
439 1.9 christos (void)snprintf(buf, sizeof(buf), "%s", strerror(errno));
440 1.9 christos return buf;
441 1.9 christos }
442 1.9 christos }
443 1.9 christos
444 1.1 phil /* Duplicate the output of last(1) */
445 1.1 phil static void
446 1.7 elric output(struct output *o)
447 1.1 phil {
448 1.9 christos time_t t = (time_t)o->o_tv.tv_sec;
449 1.1 phil printf("%-*.*s %-*.*s %-*.*s %s",
450 1.9 christos namelen, namelen, o->o_name,
451 1.9 christos linelen, linelen, o->o_line,
452 1.9 christos hostlen, hostlen, gethost(o),
453 1.9 christos t ? ctime(&t) : "Never logged in\n");
454 1.1 phil }
455 1.1 phil
456 1.1 phil static void
457 1.9 christos usage(void)
458 1.1 phil {
459 1.9 christos (void)fprintf(stderr, "Usage: %s [-nrt] [-f <filename>] "
460 1.9 christos "[-H <hostlen>] [-L <linelen>] [-N <namelen>] [user ...]\n",
461 1.9 christos getprogname());
462 1.1 phil exit(1);
463 1.1 phil }
464