lastlogin.c revision 1.13 1 1.13 atatat /* $NetBSD: lastlogin.c,v 1.13 2005/04/09 02:13:20 atatat 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.13 atatat __RCSID("$NetBSD: lastlogin.c,v 1.13 2005/04/09 02:13:20 atatat 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.12 martin if (outstack && 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.13 atatat if ((passwd = getpwuid(i)) == NULL) {
205 1.13 atatat static struct passwd p;
206 1.13 atatat static char n[32];
207 1.13 atatat snprintf(n, sizeof(n), "(%d)", i);
208 1.13 atatat p.pw_uid = i;
209 1.13 atatat p.pw_name = n;
210 1.13 atatat passwd = &p;
211 1.13 atatat }
212 1.13 atatat process_entry(passwd, &l);
213 1.1 phil }
214 1.1 phil if (ferror(fp))
215 1.1 phil warnx("fread error");
216 1.1 phil }
217 1.1 phil
218 1.9 christos (void)fclose(fp);
219 1.9 christos }
220 1.9 christos
221 1.9 christos static void
222 1.9 christos process_entry(struct passwd *p, struct lastlog *l)
223 1.9 christos {
224 1.9 christos struct output o;
225 1.9 christos
226 1.9 christos (void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name));
227 1.9 christos (void)strlcpy(o.o_line, l->ll_line, sizeof(l->ll_line));
228 1.9 christos (void)strlcpy(o.o_host, l->ll_host, sizeof(l->ll_host));
229 1.9 christos o.o_tv.tv_sec = l->ll_time;
230 1.9 christos o.o_tv.tv_usec = 0;
231 1.9 christos (void)memset(&o.o_ss, 0, sizeof(o.o_ss));
232 1.9 christos o.next = NULL;
233 1.1 phil
234 1.9 christos /*
235 1.9 christos * If we are sorting it, we need all the entries in memory so
236 1.9 christos * push the current entry onto a stack. Otherwise, we can just
237 1.9 christos * output it.
238 1.9 christos */
239 1.7 elric if (DOSORT(sortlog))
240 1.9 christos push(&o);
241 1.9 christos else
242 1.9 christos output(&o);
243 1.9 christos }
244 1.9 christos #endif
245 1.9 christos
246 1.9 christos #ifdef SUPPORT_UTMPX
247 1.9 christos static void
248 1.9 christos dolastlogx(const char *logfile, int argc, char **argv)
249 1.9 christos {
250 1.9 christos int i = 0;
251 1.9 christos DB *db = dbopen(logfile, O_RDONLY|O_SHLOCK, 0, DB_HASH, NULL);
252 1.9 christos DBT key, data;
253 1.9 christos struct lastlogx l;
254 1.9 christos struct passwd *passwd;
255 1.9 christos
256 1.9 christos if (db == NULL)
257 1.9 christos err(1, "%s", logfile);
258 1.9 christos
259 1.9 christos if (argc > 0) {
260 1.9 christos for (i = 0; i < argc; i++) {
261 1.9 christos if ((passwd = getpwnam(argv[i])) == NULL) {
262 1.9 christos warnx("User `%s' not found", argv[i]);
263 1.9 christos continue;
264 1.9 christos }
265 1.9 christos key.data = &passwd->pw_uid;
266 1.9 christos key.size = sizeof(passwd->pw_uid);
267 1.9 christos
268 1.9 christos switch ((*db->get)(db, &key, &data, 0)) {
269 1.9 christos case 0:
270 1.9 christos break;
271 1.9 christos case 1:
272 1.9 christos warnx("User `%s' not found", passwd->pw_name);
273 1.9 christos continue;
274 1.9 christos case -1:
275 1.9 christos warn("Error looking up `%s'", passwd->pw_name);
276 1.9 christos continue;
277 1.9 christos default:
278 1.9 christos abort();
279 1.9 christos }
280 1.9 christos
281 1.9 christos if (data.size != sizeof(l)) {
282 1.9 christos errno = EFTYPE;
283 1.9 christos err(1, "%s", logfile);
284 1.9 christos }
285 1.9 christos (void)memcpy(&l, data.data, sizeof(l));
286 1.9 christos
287 1.9 christos process_entryx(passwd, &l);
288 1.9 christos }
289 1.9 christos }
290 1.9 christos /* Read all lastlog entries, looking for active ones */
291 1.9 christos else {
292 1.9 christos switch ((*db->seq)(db, &key, &data, R_FIRST)) {
293 1.9 christos case 0:
294 1.9 christos break;
295 1.9 christos case 1:
296 1.9 christos warnx("No entries found");
297 1.9 christos (*db->close)(db);
298 1.9 christos return;
299 1.9 christos case -1:
300 1.9 christos warn("Error seeking to first entry");
301 1.9 christos (*db->close)(db);
302 1.9 christos return;
303 1.9 christos default:
304 1.9 christos abort();
305 1.9 christos }
306 1.9 christos
307 1.9 christos do {
308 1.9 christos uid_t uid;
309 1.9 christos
310 1.9 christos if (key.size != sizeof(uid) || data.size != sizeof(l)) {
311 1.9 christos errno = EFTYPE;
312 1.9 christos err(1, "%s", logfile);
313 1.9 christos }
314 1.9 christos (void)memcpy(&uid, key.data, sizeof(uid));
315 1.9 christos
316 1.9 christos if ((passwd = getpwuid(uid)) == NULL) {
317 1.9 christos warnx("Cannot find user for uid %lu",
318 1.9 christos (unsigned long)uid);
319 1.9 christos continue;
320 1.9 christos }
321 1.9 christos (void)memcpy(&l, data.data, sizeof(l));
322 1.9 christos process_entryx(passwd, &l);
323 1.9 christos } while ((i = (*db->seq)(db, &key, &data, R_NEXT)) == 0);
324 1.9 christos
325 1.9 christos switch (i) {
326 1.9 christos case 1:
327 1.9 christos break;
328 1.9 christos case -1:
329 1.9 christos warn("Error seeking to last entry");
330 1.9 christos break;
331 1.9 christos case 0:
332 1.9 christos default:
333 1.9 christos abort();
334 1.9 christos }
335 1.9 christos }
336 1.7 elric
337 1.9 christos (*db->close)(db);
338 1.1 phil }
339 1.1 phil
340 1.7 elric static void
341 1.9 christos process_entryx(struct passwd *p, struct lastlogx *l)
342 1.7 elric {
343 1.7 elric struct output o;
344 1.7 elric
345 1.13 atatat if (numeric > 1)
346 1.13 atatat (void)snprintf(o.o_name, sizeof(o.o_name), "%d", p->pw_uid);
347 1.13 atatat else
348 1.13 atatat (void)strlcpy(o.o_name, p->pw_name, sizeof(o.o_name));
349 1.9 christos (void)strlcpy(o.o_line, l->ll_line, sizeof(l->ll_line));
350 1.9 christos (void)strlcpy(o.o_host, l->ll_host, sizeof(l->ll_host));
351 1.9 christos (void)memcpy(&o.o_ss, &l->ll_ss, sizeof(o.o_ss));
352 1.9 christos o.o_tv = l->ll_tv;
353 1.7 elric o.next = NULL;
354 1.7 elric
355 1.7 elric /*
356 1.7 elric * If we are sorting it, we need all the entries in memory so
357 1.7 elric * push the current entry onto a stack. Otherwise, we can just
358 1.7 elric * output it.
359 1.7 elric */
360 1.7 elric if (DOSORT(sortlog))
361 1.7 elric push(&o);
362 1.7 elric else
363 1.7 elric output(&o);
364 1.7 elric }
365 1.9 christos #endif
366 1.7 elric
367 1.7 elric static void
368 1.7 elric push(struct output *o)
369 1.7 elric {
370 1.7 elric struct output *out;
371 1.7 elric
372 1.7 elric out = malloc(sizeof(*out));
373 1.7 elric if (!out)
374 1.7 elric err(EXIT_FAILURE, "malloc failed");
375 1.9 christos (void)memcpy(out, o, sizeof(*out));
376 1.7 elric out->next = NULL;
377 1.7 elric
378 1.7 elric if (outstack) {
379 1.7 elric out->next = outstack;
380 1.7 elric outstack = out;
381 1.7 elric } else {
382 1.7 elric outstack = out;
383 1.7 elric }
384 1.7 elric }
385 1.7 elric
386 1.7 elric static void
387 1.7 elric sortoutput(struct output *o)
388 1.7 elric {
389 1.7 elric struct output **outs;
390 1.7 elric struct output *tmpo;
391 1.7 elric int num;
392 1.7 elric int i;
393 1.7 elric
394 1.7 elric /* count the number of entries to display */
395 1.7 elric for (num=0, tmpo = o; tmpo; tmpo=tmpo->next, num++)
396 1.7 elric ;
397 1.7 elric
398 1.7 elric outs = malloc(sizeof(*outs) * num);
399 1.7 elric if (!outs)
400 1.7 elric err(EXIT_FAILURE, "malloc failed");
401 1.7 elric for (i=0, tmpo = o; i < num; tmpo=tmpo->next, i++)
402 1.7 elric outs[i] = tmpo;
403 1.7 elric
404 1.7 elric mergesort(outs, num, sizeof(*outs), comparelog);
405 1.7 elric
406 1.7 elric for (i=0; i < num; i++)
407 1.7 elric output(outs[i]);
408 1.7 elric }
409 1.7 elric
410 1.7 elric static int
411 1.7 elric comparelog(const void *left, const void *right)
412 1.7 elric {
413 1.7 elric struct output *l = *(struct output **)left;
414 1.7 elric struct output *r = *(struct output **)right;
415 1.7 elric int order = (sortlog&SORT_REVERSE)?-1:1;
416 1.7 elric
417 1.9 christos if (l->o_tv.tv_sec < r->o_tv.tv_sec)
418 1.7 elric return 1 * order;
419 1.9 christos if (l->o_tv.tv_sec == r->o_tv.tv_sec)
420 1.7 elric return 0;
421 1.7 elric return -1 * order;
422 1.7 elric }
423 1.7 elric
424 1.9 christos static const char *
425 1.9 christos gethost(struct output *o)
426 1.9 christos {
427 1.9 christos if (!numeric)
428 1.9 christos return o->o_host;
429 1.9 christos else {
430 1.9 christos static char buf[512];
431 1.11 christos buf[0] = '\0';
432 1.11 christos (void)sockaddr_snprintf(buf, sizeof(buf), "%a",
433 1.11 christos (struct sockaddr *)&o->o_ss);
434 1.9 christos return buf;
435 1.9 christos }
436 1.9 christos }
437 1.9 christos
438 1.1 phil /* Duplicate the output of last(1) */
439 1.1 phil static void
440 1.7 elric output(struct output *o)
441 1.1 phil {
442 1.9 christos time_t t = (time_t)o->o_tv.tv_sec;
443 1.1 phil printf("%-*.*s %-*.*s %-*.*s %s",
444 1.10 he (int)namelen, (int)namelen, o->o_name,
445 1.10 he (int)linelen, (int)linelen, o->o_line,
446 1.10 he (int)hostlen, (int)hostlen, gethost(o),
447 1.9 christos t ? ctime(&t) : "Never logged in\n");
448 1.1 phil }
449 1.1 phil
450 1.1 phil static void
451 1.9 christos usage(void)
452 1.1 phil {
453 1.9 christos (void)fprintf(stderr, "Usage: %s [-nrt] [-f <filename>] "
454 1.9 christos "[-H <hostlen>] [-L <linelen>] [-N <namelen>] [user ...]\n",
455 1.9 christos getprogname());
456 1.1 phil exit(1);
457 1.1 phil }
458