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