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