6416 ptime -p could support multiple processes
[unleashed.git] / usr / src / cmd / logins / logins.c
blobf379a2d7c46004aa08d4a31aa8cc49071ecb0a61
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
25 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
26 /* All Rights Reserved */
29 #pragma ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.15.1.2 */
32 * logins.c
34 * This file contains the source for the administrative command
35 * "logins" (available to the administrator) that produces a report
36 * containing login-IDs and other requested information.
39 #include <sys/types.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <string.h>
44 #include <ctype.h>
45 #include <grp.h>
46 #include <pwd.h>
47 #include <shadow.h>
48 #include <time.h>
49 #include <stdarg.h>
50 #include <fmtmsg.h>
51 #include <locale.h>
54 * Local constant definitions
55 * TRUE Boolean constant
56 * FALSE Boolean constant
57 * USAGE_MSG Message used to display a usage error
58 * MAXLOGINSIZE Maximum length of a valid login-ID
59 * MAXSYSTEMLOGIN Maximum value of a system user-ID.
60 * OPTSTR Options to this command
61 * ROOT_ID The user-ID of an administrator
64 #ifndef FALSE
65 #define FALSE 0
66 #endif
68 #ifndef TRUE
69 #define TRUE ((int)'t')
70 #endif
72 #define USAGE_MSG "usage: logins [-admopstux] [-g groups] [-l logins]"
73 #define MAXLOGINSIZE 14
74 #define MAXSYSTEMLOGIN 99
75 #define OPTSTR "adg:l:mopstux"
76 #define ROOT_ID 0
79 * The following macros do their function for now but will probably have
80 * to be replaced by functions sometime in the near future. The maximum
81 * system login value may someday be administerable, in which case these
82 * will have to be changed to become functions
84 * isasystemlogin Returns TRUE if the user-ID in the "struct passwd"
85 * structure referenced by the function's argument is
86 * less than or equal to the maximum value for a system
87 * user-ID, FALSE otherwise.
88 * isauserlogin Returns TRUE if the user-ID in the "struct passwd"
89 * structure referenced by the function's argument is
90 * greater than the maximum value for a system user-ID,
91 * FALSE otherwise.
94 #define isauserlogin(pw) (pw->pw_uid > MAXSYSTEMLOGIN)
95 #define isasystemlogin(pw) (pw->pw_uid <= MAXSYSTEMLOGIN)
99 * Local datatype definitions
100 * struct reqgrp Describes a group as requested through the
101 * -g option
102 * struct reqlogin Describes a login-ID as requested through
103 * the -l option
104 * struct pwdinfo Describes a password's aging information,
105 * as extracted from /etc/shadow
106 * struct secgrp Describes a login-ID's secondary group
109 /* Describes a specified group name (from the -g groups option) */
110 struct reqgrp {
111 char *groupname; /* Requested group name */
112 struct reqgrp *next; /* Next item in the list */
113 gid_t groupID; /* Group's ID */
116 /* Describes a specified login name (from the -l logins option) */
117 struct reqlogin {
118 char *loginname; /* Requested login name */
119 struct reqlogin *next; /* Next item in the list */
120 int found; /* TRUE if login in /etc/passwd */
124 * This structure describes a password's information
127 struct pwdinfo {
128 long datechg; /* Date the password was changed (mmddyy) */
129 char *passwdstatus; /* Password status */
130 long mindaystilchg; /* Min days b4 pwd can change again */
131 long maxdaystilchg; /* Max days b4 pwd can change again */
132 long warninterval; /* Days before expire to warn user */
133 long inactive; /* Lapsed days of inactivity before lock */
134 long expdate; /* Date of expiration (mmddyy) */
137 /* This structure describes secondary groups that a user belongs to */
138 struct secgrp {
139 char *groupname; /* Name of the group */
140 struct secgrp *next; /* Next item in the list */
141 gid_t groupID; /* Group-ID */
146 * These functions handle error and warning message writing.
147 * (This deals with UNIX(r) standard message generation, so
148 * the rest of the code doesn't have to.)
150 * Functions included:
151 * initmsg Initialize the message handling functions.
152 * wrtmsg Write the message using fmtmsg().
154 * Static data included:
155 * fcnlbl The label for standard messages
156 * msgbuf A buffer to contain the edited message
159 static char fcnlbl[MM_MXLABELLN+1]; /* Buffer for message label */
160 static char msgbuf[MM_MXTXTLN+1]; /* Buffer for message text */
164 * void initmsg(p)
166 * This function initializes the message handling functions.
168 * Arguments:
169 * p A pointer to a character string that is the name of the
170 * function, used to generate the label on messages. If this
171 * string contains a slash ('/'), it only uses the characters
172 * beyond the last slash in the string (this permits argv[0]
173 * to be used).
175 * Returns: Void
178 static void
179 initmsg(char *p)
181 char *q; /* Local multi-use pointer */
183 /* Use only the simple filename if there is a slash in the name */
184 if (!(q = strrchr(p, '/'))) {
185 q = p;
186 } else {
187 q++;
190 /* Build the label for messages */
191 (void) snprintf(fcnlbl, MM_MXLABELLN, "UX:%s", q);
193 /* Restrict messages to the text-component */
194 (void) putenv("MSGVERB=text");
199 * void wrtmsg(severity, action, tag, text[, txtarg1[, txtarg2[, ...]]])
201 * This function writes a message using fmtmsg()
203 * Arguments:
204 * severity The severity-component of the message
205 * action The action-string used to generate the
206 * action-component of the message
207 * tag Tag-component of the message
208 * text The text-string used to generate the text-
209 * component of the message
210 * txtarg Arguments to be inserted into the "text"
211 * string using vsprintf()
213 * Returns: Void
215 /*PRINTFLIKE4*/
216 static void
217 wrtmsg(int severity, char *action, char *tag, char *text, ...)
219 int errorflg; /* TRUE if problem generating message */
220 va_list argp; /* Pointer into vararg list */
223 /* No problems yet */
224 errorflg = FALSE;
226 /* Generate the error message */
227 va_start(argp, text);
228 if (text != MM_NULLTXT) {
229 errorflg = vsnprintf(msgbuf,
230 MM_MXTXTLN, text, argp) > MM_MXTXTLN;
232 (void) fmtmsg(MM_PRINT, fcnlbl, severity,
233 (text == MM_NULLTXT) ? MM_NULLTXT : msgbuf, action, tag);
234 va_end(argp);
237 * If there was a buffer overflow generating the error message,
238 * write a message and quit (things are probably corrupt in the
239 * static data space now
241 if (errorflg) {
242 (void) fmtmsg(MM_PRINT, fcnlbl, MM_WARNING,
243 gettext("Internal message buffer overflow"),
244 MM_NULLACT, MM_NULLTAG);
245 exit(100);
250 * These functions control the group membership list, as found in
251 * the /etc/group file.
253 * Functions included:
254 * addmember Adds a member to the membership list
255 * isamember Looks for a particular login-ID in the
256 * list of members
258 * Datatype Definitions:
259 * struct grpmember Describes a group member
261 * Static Data:
262 * membershead Pointer to the head of the list of
263 * group members
266 struct grpmember {
267 char *membername;
268 struct grpmember *next;
271 static struct grpmember *membershead;
274 * void addmember(p)
275 * char *p
277 * This function adds a member to the group member's list. The
278 * group members list is a list of structures containing a pointer
279 * to the member-name and a pointer to the next item in the
280 * structure. The structure is not ordered in any particular way.
282 * Arguments:
283 * p Pointer to the member name
285 * Returns: Void
288 static void
289 addmember(char *p)
291 struct grpmember *new; /* Member being added */
293 new = malloc(sizeof (struct grpmember));
294 new->membername = strdup(p);
295 new->next = membershead;
296 membershead = new;
301 * init isamember(p)
302 * char *p
304 * This function examines the list of group-members for the string
305 * referenced by 'p'. If 'p' is a member of the members list, the
306 * function returns TRUE. Otherwise it returns FALSE.
308 * Arguments:
309 * p Pointer to the name to search for.
311 * Returns: int
312 * TRUE If 'p' is found in the members list,
313 * FALSE otherwise
316 static int
317 isamember(char *p)
319 int found; /* TRUE if login found in list */
320 struct grpmember *pmem; /* Group member being examined */
323 /* Search the membership list for 'p' */
324 found = FALSE;
325 for (pmem = membershead; !found && pmem; pmem = pmem->next) {
326 if (strcmp(p, pmem->membername) == 0)
327 found = TRUE;
330 return (found);
335 * These functions handle the display list. The display list contains
336 * all of the information we're to display. The list contains a pointer
337 * to the login-name, a pointer to the free-field (comment), and a
338 * pointer to the next item in the list. The list is ordered alpha-
339 * betically (ascending) on the login-name field. The list initially
340 * contains a dummy field (to make insertion easier) that contains a
341 * login-name of "".
343 * Functions included:
344 * initdisp Initializes the display list
345 * adddisp Adds information to the display list
346 * isuidindisp Looks to see if a particular user-ID is in the
347 * display list
348 * genreport Generates a report from the items in the display
349 * list
350 * applygroup Add group information to the items in the display
351 * list
352 * applypasswd Add extended password information to the items
353 * in the display list
355 * Datatypes Defined:
356 * struct display Describes the structure that contains the information
357 * to be displayed. Includes pointers to the login-ID,
358 * free-field (comment), and the next structure in the
359 * list.
361 * Static Data:
362 * displayhead Pointer to the head of the display list. Initially
363 * references the null-item on the head of the list.
366 struct display {
367 char *loginID; /* Login name */
368 char *freefield; /* Free (comment) field */
369 char *groupname; /* Name of the primary group */
370 char *iwd; /* Initial working directory */
371 char *shell; /* Shell after login (may be null) */
372 struct pwdinfo *passwdinfo; /* Password information structure */
373 struct secgrp *secgrplist; /* Head of the secondary group list */
374 uid_t userID; /* User ID */
375 gid_t groupID; /* Group ID of primary group */
376 struct display *nextlogin; /* Next login in the list */
377 struct display *nextuid; /* Next user-ID in the list */
380 static struct display *displayhead;
384 * void initdisp()
386 * Initializes the display list. An empty display list contains
387 * a single element, the dummy element.
389 * Arguments: None
391 * Returns: Void
394 static void
395 initdisp(void)
397 displayhead = malloc(sizeof (struct display));
398 displayhead->nextlogin = NULL;
399 displayhead->nextuid = NULL;
400 displayhead->loginID = "";
401 displayhead->freefield = "";
402 displayhead->userID = (uid_t)-1;
407 * void adddisp(pwent)
408 * struct passwd *pwent
410 * This function adds the appropriate information from the login
411 * description referenced by 'pwent' to the list if information
412 * to be displayed. It only adds the information if the login-ID
413 * (user-name) is unique. It inserts the information in the list
414 * in such a way that the list remains ordered alphabetically
415 * (ascending) according to the login-ID (user-name).
417 * Arguments:
418 * pwent Structure that contains all of the login information
419 * of the login being added to the list. The only
420 * information that this function uses is the login-ID
421 * (user-name) and the free-field (comment field).
423 * Returns: Void
426 static void
427 adddisp(struct passwd *pwent)
429 struct display *new; /* Item being added to the list */
430 struct display *prev; /* Previous item in the list */
431 struct display *current; /* Next item in the list */
432 int found; /* FLAG, insertion point found */
433 int compare = 1; /* strcmp() compare value */
436 /* Find where this value belongs in the list */
437 prev = displayhead;
438 found = FALSE;
439 while (!found && (current = prev->nextlogin)) {
440 if ((compare = strcmp(current->loginID, pwent->pw_name)) >= 0) {
441 found = TRUE;
442 } else {
443 prev = current;
447 /* Insert this value in the list, only if it is unique though */
448 if (compare != 0) {
449 new = malloc(sizeof (struct display));
450 new->loginID = strdup(pwent->pw_name);
451 if (pwent->pw_comment && pwent->pw_comment[0] != '\0') {
452 new->freefield = strdup(pwent->pw_comment);
453 } else {
454 new->freefield = strdup(pwent->pw_gecos);
456 if (!pwent->pw_shell || !(*pwent->pw_shell)) {
457 new->shell = "/sbin/sh";
458 } else {
459 new->shell = strdup(pwent->pw_shell);
461 new->iwd = strdup(pwent->pw_dir);
462 new->userID = pwent->pw_uid;
463 new->groupID = pwent->pw_gid;
464 new->secgrplist = NULL;
465 new->passwdinfo = NULL;
466 new->groupname = NULL;
468 /* Add new display item to the list ordered by login-ID */
469 new->nextlogin = current;
470 prev->nextlogin = new;
473 * Find the appropriate place for the new item in the list
474 * ordered by userID
476 prev = displayhead;
477 found = FALSE;
478 while (!found && (current = prev->nextuid)) {
479 if (current->userID > pwent->pw_uid) {
480 found = TRUE;
481 } else {
482 prev = current;
486 /* Add the item into the list that is ordered by user-ID */
487 new->nextuid = current;
488 prev->nextuid = new;
494 * int isuidindisp(pwent)
495 * struct passwd *pwent
497 * This function examines the display list to see if the uid in
498 * the (struct passwd) referenced by "pwent" is already in the
499 * display list. It returns TRUE if it is in the list, FALSE
500 * otherwise.
502 * Since the display list is ordered by user-ID, the search continues
503 * until a match is found or a user-ID is found that is larger than
504 * the one we're searching for.
506 * Arguments:
507 * pwent Structure that contains the user-ID we're to
508 * look for
510 * Returns: int
511 * TRUE if the user-ID was found, FALSE otherwise.
514 static int
515 isuidindisp(struct passwd *pwent)
517 struct display *dp;
521 * Search the list, beginning at the beginning (where else?)
522 * and stopping when the user-ID is found or one is found that
523 * is greater than the user-ID we're searching for. Recall
524 * that this list is ordered by user-ID
527 for (dp = displayhead->nextuid; dp && (dp->userID < pwent->pw_uid);
528 dp = dp->nextuid) {
529 continue;
533 * If the pointer "dp" points to a structure that has a
534 * matching user-ID, return TRUE. Otherwise FALSE
536 return (dp && (dp->userID == pwent->pw_uid));
541 * void applygroup(allgroups)
542 * int allgroups
544 * This function applies group information to the login-IDs in the
545 * display list. It always applies the primary group information.
546 * If "allgroups" is TRUE, it applies secondary information as well.
548 * Arguments:
549 * allgroups FLAG. TRUE if secondary group info is to be
550 * applied -- FALSE otherwise.
552 * Returns: void
555 static void
556 applygroup(int allgroups)
558 struct display *dp; /* Display list running ptr */
559 struct group *grent; /* Group info, from getgrent() */
560 char *p; /* Temp char pointer */
561 char **pp; /* Temp char * pointer */
562 int compare; /* Value from strcmp() */
563 int done; /* TRUE if finished, FALSE otherwise */
564 struct secgrp *psecgrp; /* Block allocated for this info */
565 struct secgrp *psrch; /* Secondary group -- for searching */
567 if (!allgroups) {
568 /* short circute getting all the groups */
569 for (dp = displayhead->nextuid; dp; dp = dp->nextuid) {
570 if ((grent = getgrgid(dp->groupID)) != NULL) {
571 dp->groupname = strdup(grent->gr_name);
574 return;
577 /* For each group-ID in the /etc/group file ... */
578 while (grent = getgrent()) {
580 * Set the primary group for the login-IDs in the display
581 * list. For each group-ID we get, leaf through the display
582 * list and set the group-name if the group-IDs match
585 p = NULL;
586 for (dp = displayhead->nextuid; dp; dp = dp->nextuid) {
587 if ((dp->groupID == grent->gr_gid) && !dp->groupname) {
588 if (!p) {
589 p = strdup(grent->gr_name);
591 dp->groupname = p;
596 * If we're to be displaying secondary group membership,
597 * leaf through the list of group members. Then, attempt
598 * to find that member in the display list. When found,
599 * attach secondary group info to the user.
602 for (pp = grent->gr_mem; *pp; pp++) {
603 done = FALSE;
604 for (dp = displayhead->nextlogin; !done && dp;
605 dp = dp->nextlogin) {
606 if (((compare = strcmp(dp->loginID,
607 *pp)) == 0) &&
608 !(grent->gr_gid == dp->groupID)) {
609 if (!p) {
610 p = strdup(grent->gr_name);
612 psecgrp = malloc(
613 sizeof (struct secgrp));
614 psecgrp->groupID = grent->gr_gid;
615 psecgrp->groupname = p;
616 psecgrp->next = NULL;
617 if (!dp->secgrplist) {
618 dp->secgrplist = psecgrp;
619 } else {
620 for (psrch = dp->secgrplist;
621 psrch->next;
622 psrch = psrch->next) {
623 continue;
625 psrch->next = psecgrp;
627 done = TRUE;
628 } else if (compare > 0) {
629 done = TRUE;
635 /* Close the /etc/group file */
636 endgrent();
641 * void applypasswd()
643 * This function applies extended password information to an item
644 * to be displayed. It allocates space for a structure describing
645 * the password, then fills in that structure from the information
646 * in the /etc/shadow file.
648 * Arguments: None
650 * Returns: Void
653 static void
654 applypasswd(void)
656 struct pwdinfo *ppasswd; /* Ptr to pwd desc in current element */
657 struct display *dp; /* Ptr to current element */
658 struct spwd *psp; /* Pointer to a shadow-file entry */
659 struct tm *ptm; /* Pointer to a time-of-day structure */
660 time_t pwchg; /* System-time of last pwd chg */
661 time_t pwexp; /* System-time of password expiration */
664 /* Make sure the shadow file is rewound */
665 setspent();
669 * For each item in the display list...
672 for (dp = displayhead->nextuid; dp; dp = dp->nextuid) {
674 /* Allocate structure space for the password description */
675 ppasswd = malloc(sizeof (struct pwdinfo));
676 dp->passwdinfo = ppasswd;
679 * If there's no entry in the /etc/shadow file, assume
680 * that the login is locked
683 if ((psp = getspnam(dp->loginID)) == NULL) {
684 pwchg = 0L; /* Epoch */
685 ppasswd->passwdstatus = "LK"; /* LK, Locked */
686 ppasswd->mindaystilchg = 0L;
687 ppasswd->maxdaystilchg = 0L;
688 ppasswd->warninterval = 0L;
689 ppasswd->inactive = 0L;
690 pwexp = 0L;
691 } else {
693 * Otherwise, fill in the password information from the
694 * info in the shadow entry
696 if (psp->sp_pwdp == NULL || (*psp->sp_pwdp) == '\0')
697 ppasswd->passwdstatus = "NP";
698 else if (strncmp(psp->sp_pwdp, LOCKSTRING,
699 sizeof (LOCKSTRING)-1) == 0)
700 ppasswd->passwdstatus = "LK";
701 else if (strncmp(psp->sp_pwdp, NOLOGINSTRING,
702 sizeof (NOLOGINSTRING)-1) == 0)
703 ppasswd->passwdstatus = "NL";
704 else if ((strlen(psp->sp_pwdp) == 13 &&
705 psp->sp_pwdp[0] != '$') ||
706 psp->sp_pwdp[0] == '$')
707 ppasswd->passwdstatus = "PS";
708 else
709 ppasswd->passwdstatus = "UN";
711 * Set up the last-changed date,
712 * the minimum days between changes,
713 * the maximum life of a password,
714 * the interval before expiration that the user
715 * is warned,
716 * the number of days a login can be inactive before
717 * it expires, and the login expiration date
720 pwchg = psp->sp_lstchg;
721 ppasswd->mindaystilchg = psp->sp_min;
722 ppasswd->maxdaystilchg = psp->sp_max;
723 ppasswd->warninterval = psp->sp_warn;
724 ppasswd->inactive = psp->sp_inact;
725 pwexp = psp->sp_expire;
729 * Convert the date of the last password change from days-
730 * since-epoch to mmddyy (integer) form. Involves the
731 * intermediate step of converting the date from days-
732 * since-epoch to seconds-since-epoch. We'll set this to
733 * somewhere near the middle of the day, since there are not
734 * always 24*60*60 seconds in a year. (Yeech)
736 * Note: The form mmddyy should probably be subject to
737 * internationalization -- Non-Americans will think that
738 * 070888 is 07 August 88 when the software is trying to say
739 * 08 July 88. Systems Engineers seem to think that this isn't
740 * a problem though...
743 if (pwchg != -1L) {
744 pwchg = (pwchg * DAY) + (DAY/2);
745 ptm = localtime(&pwchg);
746 ppasswd->datechg = ((long)(ptm->tm_mon+1) * 10000L) +
747 (long)((ptm->tm_mday * 100) +
748 (ptm->tm_year % 100));
749 } else {
750 ppasswd->datechg = 0L;
754 * Convert the passwd expiration date from days-since-epoch
755 * to mmddyy (long integer) form using the same algorithm and
756 * comments as above.
759 if (pwexp != -1L) {
760 pwexp = (pwexp * DAY) + (DAY/2);
761 ptm = localtime(&pwexp);
762 ppasswd->expdate = ((long)(ptm->tm_mon+1) * 10000L) +
763 (long)((ptm->tm_mday * 100) +
764 (ptm->tm_year % 100));
765 } else {
766 ppasswd->expdate = 0L;
770 /* Close the shadow password file */
771 endspent();
776 * int hasnopasswd(pwent)
777 * struct passwd *pwent
779 * This function examines a login's password-file entry
780 * and, if necessary, its shadow password-file entry and
781 * returns TRUE if that user-ID has no password, meaning
782 * that the user-ID can be used to log into the system
783 * without giving a password. The function returns FALSE
784 * otherwise.
786 * Arguments:
787 * pwent Login to examine.
789 * Returns: int
790 * TRUE if the login can be used without a password, FALSE
791 * otherwise.
794 static int
795 hasnopasswd(struct passwd *pwent)
797 struct spwd *psp; /* /etc/shadow file struct */
798 int nopwflag; /* TRUE if login has no passwd */
801 * A login has no password if:
802 * 1. There exists an entry for that login in the
803 * shadow password-file (/etc/passwd), and
804 * 2. The encrypted password in the structure describing
805 * that entry is either: NULL or a null string ("")
808 /* Get the login's entry in the shadow password file */
809 if (psp = getspnam(pwent->pw_name)) {
811 /* Look at the encrypted password in that entry */
812 if (psp->sp_pwdp == (char *)0 ||
813 *psp->sp_pwdp == '\0') {
814 nopwflag = TRUE;
815 } else {
816 nopwflag = FALSE;
818 } else {
819 nopwflag = FALSE;
822 /* Done ... */
823 return (nopwflag);
828 * void writeunformatted(current, xtndflag, expflag)
829 * struct display *current
830 * int xtndflag
831 * int expflag
833 * This function writes the data in the display structure "current"
834 * to the standard output file. It writes the information in the
835 * form of a colon-list. It writes secondary group information if
836 * that information is in the structure, it writes extended
837 * (initial working directory, shell, and password-aging) info
838 * if the "xtndflag" is TRUE, and it writes password expiration
839 * information if "expflag" is TRUE.
841 * Arguments:
842 * current Structure containing information to write.
843 * xtndflag TRUE if extended information is to be written,
844 * FALSE otherwise
845 * expflag TRUE if password expiration information is to
846 * be written, FALSE otherwise
848 * Returns: void
851 static void
852 writeunformatted(struct display *current, int xtndflag, int expflag)
854 struct secgrp *psecgrp; /* Secondary group info */
855 struct pwdinfo *pwdinfo; /* Password aging info */
857 /* Write the general information */
858 (void) fprintf(stdout, "%s:%u:%s:%u:%s",
859 current->loginID,
860 current->userID,
861 current->groupname == NULL ? "" : current->groupname,
862 current->groupID,
863 current->freefield);
866 * If the group information is there, write it (it's only
867 * there if it's supposed to be written)
869 for (psecgrp = current->secgrplist; psecgrp; psecgrp = psecgrp->next) {
870 (void) fprintf(stdout, ":%s:%u",
871 psecgrp->groupname, psecgrp->groupID);
874 /* If the extended info flag is TRUE, write the extended information */
875 if (xtndflag) {
876 pwdinfo = current->passwdinfo;
877 (void) fprintf(stdout, ":%s:%s:%s:%6.6ld:%ld:%ld:%ld",
878 current->iwd, current->shell,
879 pwdinfo->passwdstatus,
880 pwdinfo->datechg,
881 pwdinfo->mindaystilchg, pwdinfo->maxdaystilchg,
882 pwdinfo->warninterval);
885 /* If the password expiration information is requested, write it. */
886 if (expflag) {
887 pwdinfo = current->passwdinfo;
888 (void) fprintf(stdout, ":%ld:%ld",
889 pwdinfo->inactive, pwdinfo->expdate);
892 /* Terminate the information with a new-line */
893 (void) putc('\n', stdout);
898 * void writeformatted(current, xtndflag, expflag)
899 * struct display *current
900 * int xtndflag
901 * int expflag
903 * This function writes the data in the display structure "current"
904 * to the standard output file. It writes the information in an
905 * easily readable format. It writes secondary group information
906 * if that information is in the structure, it writes extended
907 * (initial working directory, shell, and password-aging) info if
908 * "xtndflag" is TRUE, and it write password expiration information
909 * if "expflag" is TRUE.
911 * Arguments:
912 * current Structure containing info to write.
913 * xtndflag TRUE if extended information to be written,
914 * FALSE otherwise
915 * expflag TRUE if password expiration information to be written,
916 * FALSE otherwise
918 * Returns: void
921 static void
922 writeformatted(struct display *current, int xtndflag, int expflag)
924 struct secgrp *psecgrp; /* Secondary group info */
925 struct pwdinfo *pwdinfo; /* Password aging info */
927 /* Write general information */
928 (void) fprintf(stdout, "%-14s %-6u %-14s %-6u %s\n",
929 current->loginID, current->userID,
930 current->groupname == NULL ? "" : current->groupname,
931 current->groupID, current->freefield);
934 * Write information about secondary groups if the info exists
935 * (it only exists if it is to be written)
937 for (psecgrp = current->secgrplist; psecgrp; psecgrp = psecgrp->next) {
938 (void) fprintf(stdout, " %-14s %-6u\n",
939 psecgrp->groupname, psecgrp->groupID);
943 * If the extended information flag is TRUE,
944 * write the extended information
947 if (xtndflag) {
948 pwdinfo = current->passwdinfo;
949 (void) fprintf(stdout, " %s\n",
950 current->iwd);
951 (void) fprintf(stdout, " %s\n",
952 current->shell);
953 (void) fprintf(stdout, " %s "
954 "%6.6ld %ld %ld %ld\n",
955 pwdinfo->passwdstatus,
956 pwdinfo->datechg, pwdinfo->mindaystilchg,
957 pwdinfo->maxdaystilchg,
958 pwdinfo->warninterval);
962 * If the password expiration info flag is TRUE,
963 * write that information
965 if (expflag) {
966 pwdinfo = current->passwdinfo;
967 (void) fprintf(stdout, " %ld %6.6ld\n",
968 pwdinfo->inactive, pwdinfo->expdate);
974 * void genuidreport(pipeflag, xtndflag, expflag)
975 * int pipeflag
976 * int xtndflag
977 * int expflag
979 * This function generates a report on the standard output
980 * stream (stdout) containing the login-IDs in the list of
981 * logins built by this command. The list is ordered based
982 * on user-ID. If the <pipeflag> variable is not zero, it
983 * will generate a report containing parsable records.
984 * Otherwise, it will generate a columnarized report. If
985 * the <xtndflag> variable is not zero, it will include the
986 * extended set of information (password aging info, home
987 * directory, shell process, etc.). If <expflag> is not
988 * zero, it will display password expiration information.
990 * Arguments:
991 * pipeflag int
992 * TRUE if a parsable report is needed,
993 * FALSE if a columnar report is needed
994 * xtndflag int
995 * TRUE if extended set of info is to be displayed,
996 * FALSE otherwise
997 * expflag int
998 * TRUE if password expiration information is to be
999 * displayed, FALSE otherwise
1001 * Returns: void
1004 static void
1005 genuidreport(int pipeflag, int xtndflag, int expflag)
1008 struct display *current; /* Data being displayed */
1012 * Initialization for loop.
1013 * (NOTE: The first element in the list of logins to display is
1014 * a dummy element.)
1016 current = displayhead;
1019 * Display elements in the list
1021 if (pipeflag) {
1022 for (current = displayhead->nextuid; current;
1023 current = current->nextuid) {
1024 writeunformatted(current, xtndflag, expflag);
1026 } else {
1027 for (current = displayhead->nextuid; current;
1028 current = current->nextuid) {
1029 writeformatted(current, xtndflag, expflag);
1036 * void genlogreport(pipeflag, xtndflag, expflag)
1037 * int pipeflag
1038 * int xtndflag
1039 * int expflag
1041 * This function generates a report on the standard output
1042 * stream (stdout) containing the login-IDs in the list of
1043 * logins built by this command. The list is ordered based
1044 * on user name. If the <pipeflag> variable is not zero, it
1045 * will generate a report containing parsable records.
1046 * Otherwise, it will generate a columnarized report. If
1047 * the <xtndflag> variable is not zero, it will include the
1048 * extended set of information (password aging info, home
1049 * directory, shell process, etc.). If <expflag> is not
1050 * zero, it will include password expiration information.
1052 * Arguments:
1053 * pipeflag int
1054 * TRUE if a parsable report is needed,
1055 * FALSE if a columnar report is needed
1056 * xtndflag int
1057 * TRUE if extended set of info is to be displayed,
1058 * FALSE otherwise
1059 * expflag int
1060 * TRUE if password expiration information is to
1061 * be displayed, FALSE otherwise
1063 * Returns: void
1066 static void
1067 genlogreport(int pipeflag, int xtndflag, int expflag)
1069 struct display *p; /* Value being displayed */
1073 * Initialization for loop.
1074 * (NOTE: The first element in the list of logins to display is
1075 * a dummy element.)
1077 p = displayhead;
1080 * Display elements in the list
1082 if (pipeflag) {
1083 for (p = displayhead->nextlogin; p; p = p->nextlogin) {
1084 writeunformatted(p, xtndflag, expflag);
1086 } else {
1087 for (p = displayhead->nextlogin; p; p = p->nextlogin) {
1088 writeformatted(p, xtndflag, expflag);
1093 struct localpw {
1094 struct localpw *next;
1095 struct passwd pw;
1098 struct localpw *pwtable = NULL;
1100 /* Local passwd pointer for getpwent() -- -1 means not in use, NULL for EOF */
1101 struct localpw *pwptr;
1103 int in_localgetpwent = 0; /* Set if in local_getpwent */
1105 static struct localpw *
1106 fill_localpw(struct localpw *lpw, struct passwd *pw) {
1107 struct localpw *cur;
1110 * Copy the data -- we have to alloc areas for it all
1112 lpw->pw.pw_name = strdup(pw->pw_name);
1113 lpw->pw.pw_passwd = strdup(pw->pw_passwd);
1114 lpw->pw.pw_uid = pw->pw_uid;
1115 lpw->pw.pw_gid = pw->pw_gid;
1116 lpw->pw.pw_age = strdup(pw->pw_age);
1117 lpw->pw.pw_comment = strdup(pw->pw_comment);
1118 lpw->pw.pw_gecos = strdup(pw->pw_gecos);
1119 lpw->pw.pw_dir = strdup(pw->pw_dir);
1120 lpw->pw.pw_shell = strdup(pw->pw_shell);
1122 cur = lpw;
1123 lpw->next = malloc(sizeof (struct localpw));
1124 return (cur);
1127 void
1128 build_localpw(struct reqlogin *req_head)
1130 struct localpw *next, *cur;
1131 struct passwd *pw;
1132 struct reqlogin *req_next;
1134 next = malloc(sizeof (struct localpw));
1136 pwtable = next;
1138 req_next = req_head;
1140 while (req_next != NULL) {
1141 if ((pw = getpwnam(req_next->loginname)) != NULL) {
1143 * Copy the data -- we have to alloc areas for it all
1145 cur = fill_localpw(next, pw);
1146 req_next->found = TRUE;
1147 next = cur->next;
1150 req_next = req_next->next;
1153 if (req_head == NULL) {
1154 while ((pw = getpwent()) != NULL) {
1156 * Copy the data -- we have to alloc areas for it all
1158 cur = fill_localpw(next, pw);
1159 next = cur->next;
1163 if (pwtable == next) {
1164 pwtable = NULL;
1165 } else {
1166 free(next);
1167 cur->next = NULL;
1170 endpwent();
1173 struct passwd *
1174 local_getpwent(void)
1176 if (!in_localgetpwent) {
1177 in_localgetpwent = 1;
1178 pwptr = pwtable;
1179 } else if (pwptr != NULL) {
1180 pwptr = pwptr->next;
1183 if (pwptr != NULL)
1184 return (&(pwptr->pw));
1185 else
1186 return (NULL);
1189 void
1190 local_endpwent(void)
1192 in_localgetpwent = 0;
1195 long
1196 local_pwtell(void)
1198 return ((long)pwptr);
1201 void
1202 local_pwseek(long ptr)
1204 pwptr = (struct localpw *)ptr;
1208 * logins [-admopstux] [-l logins] [-g groups]
1210 * This command generates a report of logins administered on
1211 * the system. The list will contain logins that meet criteria
1212 * described by the options in the list. If there are no options,
1213 * it will list all logins administered. It is intended to be used
1214 * only by administrators.
1216 * Options:
1217 * -a Display password expiration information.
1218 * -d list all logins that share user-IDs with another
1219 * login.
1220 * -g groups specifies the names of the groups to which a login
1221 * must belong before it is included in the generated
1222 * list. "groups" is a comma-list of group names.
1223 * -l logins specifies the logins to display. "logins" is a
1224 * comma-list of login names.
1225 * -m in addition to the usual information, for each
1226 * login displayed, list all groups to which that
1227 * login is member.
1228 * -o generate a report as a colon-list instead of in a
1229 * columnar format
1230 * -p list all logins that have no password.
1231 * -s list all system logins
1232 * -t sort the report lexicographically by login name
1233 * instead of by user-ID
1234 * -u list all user logins
1235 * -x in addition to the usual information, display an
1236 * extended set of information that includes the home
1237 * directory, initial process, and password status and
1238 * aging information
1240 * Exit Codes:
1241 * 0 All's well that ends well
1242 * 1 Usage error
1246 main(int argc, char *argv[])
1248 struct passwd *plookpwd; /* Ptr to searcher pw (-d) */
1249 struct reqgrp *reqgrphead; /* Head of the req'd group list */
1250 struct reqgrp *pgrp; /* Current item in req'd group list */
1251 struct reqgrp *qgrp; /* Prev item in the req'd group list */
1252 struct reqlogin *reqloginhead; /* Head of req'd login list */
1253 struct reqlogin *plogin; /* Current item in req'd login list */
1254 struct reqlogin *qlogin; /* Prev item in req'd login list */
1255 struct passwd *pwent; /* /etc/passwd entry */
1256 struct group *grent; /* /etc/group entry */
1257 char *token; /* Token extracted by strtok() */
1258 char **pp; /* Group member */
1259 char *g_arg; /* -g option's argument */
1260 char *l_arg; /* -l option's argument */
1261 long lookpos; /* File pos'n, rec we're looking for */
1262 int a_seen; /* Is -a requested? */
1263 int d_seen; /* Is -d requested? */
1264 int g_seen; /* Is -g requested? */
1265 int l_seen; /* Is -l requested? */
1266 int m_seen; /* Is -m requested? */
1267 int o_seen; /* Is -o requested? */
1268 int p_seen; /* Is -p requested? */
1269 int s_seen; /* Is -s requested? */
1270 int t_seen; /* Is -t requested? */
1271 int u_seen; /* Is -u requested? */
1272 int x_seen; /* Is -x requested? */
1273 int errflg; /* Is there a command-line problem */
1274 int done; /* Is the process (?) is complete */
1275 int groupcount; /* Number of groups specified */
1276 int doall; /* Are all logins to be reported */
1277 int c; /* Character returned from getopt() */
1279 (void) setlocale(LC_ALL, "");
1281 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
1282 #define TEXT_DOMAIN "SYS_TEST"
1283 #endif
1284 (void) textdomain(TEXT_DOMAIN);
1286 /* Initializations */
1287 initmsg(argv[0]);
1291 /* Command-line processing */
1293 /* Initializations */
1294 a_seen = FALSE;
1295 d_seen = FALSE;
1296 g_seen = FALSE;
1297 l_seen = FALSE;
1298 m_seen = FALSE;
1299 o_seen = FALSE;
1300 p_seen = FALSE;
1301 s_seen = FALSE;
1302 t_seen = FALSE;
1303 u_seen = FALSE;
1304 x_seen = FALSE;
1305 errflg = FALSE;
1306 opterr = 0;
1307 while (!errflg && ((c = getopt(argc, argv, OPTSTR)) != EOF)) {
1309 /* Case on the option character */
1310 switch (c) {
1313 * -a option:
1314 * Display password expiration information
1317 case 'a':
1318 if (a_seen)
1319 errflg = TRUE;
1320 else
1321 a_seen = TRUE;
1322 break;
1325 * -d option:
1326 * Display logins which share user-IDs with other logins
1329 case 'd':
1330 if (d_seen)
1331 errflg = TRUE;
1332 else
1333 d_seen = TRUE;
1334 break;
1337 * -g <groups> option:
1338 * Display the specified groups
1341 case 'g':
1342 if (g_seen) {
1343 errflg = TRUE;
1344 } else {
1345 g_seen = TRUE;
1346 g_arg = optarg;
1348 break;
1351 * -l <logins> option:
1352 * Display the specified logins
1355 case 'l':
1356 if (l_seen) {
1357 errflg = TRUE;
1358 } else {
1359 l_seen = TRUE;
1360 l_arg = optarg;
1362 break;
1365 * -m option:
1366 * Display multiple group information
1369 case 'm':
1370 if (m_seen)
1371 errflg = TRUE;
1372 else
1373 m_seen = TRUE;
1374 break;
1377 * -o option:
1378 * Display information as a colon-list
1381 case 'o':
1382 if (o_seen)
1383 errflg = TRUE;
1384 else
1385 o_seen = TRUE;
1386 break;
1389 * -p option:
1390 * Select logins that have no password
1393 case 'p':
1394 if (p_seen)
1395 errflg = TRUE;
1396 else
1397 p_seen = TRUE;
1398 break;
1401 * -s option:
1402 * Select system logins
1405 case 's':
1406 if (s_seen)
1407 errflg = TRUE;
1408 else
1409 s_seen = TRUE;
1410 break;
1413 * -t option:
1414 * Sort alphabetically by login-ID instead of numerically
1415 * by user-ID
1418 case 't':
1419 if (t_seen)
1420 errflg = TRUE;
1421 else
1422 t_seen = TRUE;
1423 break;
1426 * -u option:
1427 * Select user logins
1430 case 'u':
1431 if (u_seen)
1432 errflg = TRUE;
1433 else
1434 u_seen = TRUE;
1435 break;
1438 * -x option:
1439 * Display extended info (init working dir, shell, pwd info)
1442 case 'x':
1443 if (x_seen)
1444 errflg = TRUE;
1445 else
1446 x_seen = TRUE;
1447 break;
1449 default: /* Oops.... */
1450 errflg = TRUE;
1454 /* Write out a usage message if necessary and quit */
1455 if (errflg || (optind != argc)) {
1456 wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, gettext(USAGE_MSG));
1457 exit(1);
1461 * The following section does preparation work, setting up for
1462 * building the list of logins to display
1467 * If -l logins is on the command line, build a list of
1468 * logins we're to generate reports for.
1471 if (l_seen) {
1472 reqloginhead = NULL;
1473 if (token = strtok(l_arg, ",")) {
1474 plogin = malloc(sizeof (struct reqlogin));
1475 plogin->loginname = token;
1476 plogin->found = FALSE;
1477 plogin->next = NULL;
1478 reqloginhead = plogin;
1479 qlogin = plogin;
1480 while (token = strtok(NULL, ",")) {
1481 plogin = malloc(sizeof (struct reqlogin));
1482 plogin->loginname = token;
1483 plogin->found = FALSE;
1484 plogin->next = NULL;
1485 qlogin->next = plogin;
1486 qlogin = plogin;
1490 * Build an in-core structure of just the passwd database
1491 * entries requested. This greatly reduces the time
1492 * to get all entries and filter later.
1494 build_localpw(reqloginhead);
1495 } else {
1497 * Build an in-core structure of all passwd database
1498 * entries. This is important since we have to assume that
1499 * getpwent() is going out to one or more network name
1500 * services that could be changing on the fly. This will
1501 * limit us to one pass through the network data.
1503 build_localpw(NULL);
1507 * If the -g groups option was on the command line, build a
1508 * list containing groups we're to list logins for.
1511 if (g_seen) {
1512 groupcount = 0;
1513 reqgrphead = NULL;
1514 if (token = strtok(g_arg, ",")) {
1515 pgrp = malloc(sizeof (struct reqgrp));
1516 pgrp->groupname = token;
1517 pgrp->next = NULL;
1518 groupcount++;
1519 reqgrphead = pgrp;
1520 qgrp = pgrp;
1521 while (token = strtok(NULL, ",")) {
1522 pgrp = malloc(sizeof (struct reqgrp));
1523 pgrp->groupname = token;
1524 pgrp->next = NULL;
1525 groupcount++;
1526 qgrp->next = pgrp;
1527 qgrp = pgrp;
1534 * Generate the list of login information to display
1537 /* Initialize the login list */
1538 membershead = NULL;
1542 * If -g groups was specified, generate a list of members
1543 * of the specified groups
1546 if (g_seen) {
1547 /* For each group mentioned with the -g option ... */
1548 for (pgrp = reqgrphead; (groupcount > 0) && pgrp;
1549 pgrp = pgrp->next) {
1550 if ((grent = getgrnam(pgrp->groupname)) != NULL) {
1552 * Remembering the group-ID for later
1555 groupcount--;
1556 pgrp->groupID = grent->gr_gid;
1557 for (pp = grent->gr_mem; *pp; pp++) {
1558 addmember(*pp);
1560 } else {
1561 wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG,
1562 gettext("%s was not found"),
1563 pgrp->groupname);
1569 /* Initialize the list of logins to display */
1570 initdisp();
1574 * Add logins that have user-IDs that are used more than once,
1575 * if requested. This command is pretty slow, since the algorithm
1576 * reads from the /etc/passwd file 1+2+3+...+n times where n is the
1577 * number of login-IDs in the /etc/passwd file. (Actually, this
1578 * can be optimized so it's not quite that bad, but the order or
1579 * magnitude stays the same.)
1581 * Note: This processing needs to be done before any other options
1582 * are processed -- the algorithm contains an optimization
1583 * that insists on the display list being empty before this
1584 * option is processed.
1587 if (d_seen) {
1590 * The following code is a quick&dirty reimplementation of the
1591 * original algorithm, which opened the password file twice (to
1592 * get two file pointer into the data) and then used fgetpwent()
1593 * in undocumented ways to scan through the file, checking for
1594 * duplicates. This does not work when getpwent() is used to
1595 * go out over the network, since there is not file pointer.
1597 * Instead an in-memory list of passwd structures is built,
1598 * and then this list is scanned. The routines
1599 * Local_getpwent(), etc., are designed to mimic the standard
1600 * library routines, so this code does not have to be
1601 * extensively modified.
1605 * For reference, here is the original comment about the next
1606 * section of code. Some of the code has changed, but the
1607 * algorithm is the same:
1609 * Open the system password file once. This instance will be
1610 * used to leaf through the file once, reading each entry once,
1611 * and searching the remainder of the file for another login-ID
1612 * that has the same user-ID. Note that there are lots of
1613 * contortions one has to go through when reading two instances
1614 * of the /etc/passwd file. That's why there's some seeking,
1615 * re-reading of the same record, and other junk. Luckily, this
1616 * feature won't be requested very often, and still isn't too
1617 * slow...
1620 /* For each entry in the passwd database ... */
1621 while (plookpwd = local_getpwent()) {
1623 * Optimization -- If the login's user-ID is already
1624 * in the display list, there's no reason to process
1625 * this entry -- it's already there.
1627 if (!isuidindisp(plookpwd)) {
1629 * Rememeber the current entry's position,
1630 * so when we finish scanning through the
1631 * database looking for duplicates we can
1632 * return to the current place, so that the
1633 * enclosing loop will march in an orderly
1634 * fashion through the passwd database.
1636 done = FALSE;
1637 lookpos = local_pwtell();
1640 * For each record in the passwd database
1641 * beyond the searching record ...
1643 while (pwent = local_getpwent()) {
1646 * If there's a match between the
1647 * searcher's user-ID and the
1648 * searchee's user-ID ...
1650 if (pwent->pw_uid == plookpwd->pw_uid) {
1652 * If this is the first
1653 * duplicate of this searcher
1654 * that we find,
1655 * add the searcher's
1656 * record to the display list
1657 * (It wants to be on the
1658 * list first to avoid
1659 * ordering "flakeyness")
1661 if (done == FALSE) {
1662 adddisp(plookpwd);
1663 done = TRUE;
1667 * Now add the searchee's
1668 * record
1670 adddisp(pwent);
1674 /* Reposition to searcher record */
1675 local_pwseek(lookpos);
1679 local_endpwent();
1684 * Loop through the passwd database squirelling away the
1685 * information we need for the display.
1687 * NOTE: Once a login is added to the list, the rest of the
1688 * body of the loop is bypassed (via a continue statement).
1691 doall = !(s_seen || u_seen || p_seen || d_seen || l_seen || g_seen);
1693 if (doall || s_seen || u_seen || p_seen || l_seen || g_seen) {
1695 while (pwent = local_getpwent()) {
1696 done = FALSE;
1699 * If no user-specific options were specified,
1700 * include this login-ID
1702 if (doall) {
1703 adddisp(pwent);
1704 continue;
1708 * If the user specified system login-IDs,
1709 * and this is a system ID, include it
1711 if (s_seen) {
1712 if (isasystemlogin(pwent)) {
1713 adddisp(pwent);
1714 continue;
1719 * If the user specified user login-IDs,
1720 * and this is a user ID, include it
1722 if (u_seen) {
1723 if (isauserlogin(pwent)) {
1724 adddisp(pwent);
1725 continue;
1730 * If the user is asking for login-IDs that have
1731 * no password, and this one has no password, include it
1733 if (p_seen) {
1734 if (hasnopasswd(pwent)) {
1735 adddisp(pwent);
1736 continue;
1741 * If specific logins were requested, leaf through
1742 * the list of logins they requested. If this login
1743 * is on the list, include it.
1745 if (l_seen) {
1746 for (plogin = reqloginhead; !done && plogin;
1747 plogin = plogin->next) {
1748 if (strcmp(pwent->pw_name,
1749 plogin->loginname) == 0) {
1750 plogin->found = TRUE;
1751 adddisp(pwent);
1752 done = TRUE;
1755 if (done)
1756 continue;
1760 * If specific groups were requested, leaf through the
1761 * list of login-IDs that belong to those groups.
1762 * If this login-ID is in that list, or its primary
1763 * group is one of those requested, include it.
1766 if (g_seen) {
1767 for (pgrp = reqgrphead; !done && pgrp;
1768 pgrp = pgrp->next) {
1769 if (pwent->pw_gid == pgrp->groupID) {
1770 adddisp(pwent);
1771 done = TRUE;
1774 if (!done && isamember(pwent->pw_name)) {
1775 adddisp(pwent);
1776 done = TRUE;
1779 if (done)
1780 continue;
1783 local_endpwent();
1786 /* Let the user know about logins they requested that don't exist */
1787 if (l_seen) {
1788 for (plogin = reqloginhead; plogin; plogin = plogin->next) {
1789 if (!plogin->found) {
1790 wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG,
1791 gettext("%s was not found"),
1792 plogin->loginname);
1797 /* Apply group information */
1798 applygroup(m_seen);
1802 * Apply password information (only needed if the extended
1803 * set of information has been requested)
1805 if (x_seen || a_seen)
1806 applypasswd();
1810 * Generate a report from this display items we've squirreled away
1813 if (t_seen)
1814 genlogreport(o_seen, x_seen, a_seen);
1815 else
1816 genuidreport(o_seen, x_seen, a_seen);
1818 /* We're through! */
1819 return (0);