/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * users.c * * This file contains the source for the administrative command * "listusers" (available to the general user population) that * produces a report containing user login-IDs and their "free * field" (typically contains the user's name and other information). */ /* * Header files referenced: * sys/types.h System type definitions * stdio.h Definitions for standard I/O functions and constants * string.h Definitions for string-handling functions * grp.h Definitions for referencing the /etc/group file * pwd.h Definitions for referencing the /etc/passwd file * varargs.h Definitions for using a variable argument list * fmtmsg.h Definitions for using the standard message formatting * facility */ #include #include #include #include #include #include #include #include /* * Externals referenced (and not defined by a header file): * malloc Allocate memory from main memory * getopt Extract the next option from the command line * optind The argument count of the next option to extract from * the command line * optarg A pointer to the argument of the option just extracted * from the command line * opterr FLAG: !0 tells getopt() to write an error message if * it detects an error * getpwent Get next entry from the /etc/passwd file * getgrent Get next entry from the /etc/group file * fmtmsg Standard message generation facility * putenv Modify the environment * exit Exit the program */ /* * Local constant definitions */ #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE ('t') #endif #define USAGE_MSG "usage: listusers [-g groups] [-l logins]" #define MAXLOGINSIZE 14 #define LOGINFIELDSZ MAXLOGINSIZE+2 #define isauserlogin(uid) (uid >= 100) #define isasystemlogin(uid) (uid < 100) #define isausergroup(gid) (gid >= 100) #define isasystemgroup(gid) (gid < 100) /* * Local datatype definitions */ /* * This structure describes a specified group name * (from the -g groups option) */ struct reqgrp { char *groupname; struct reqgrp *next; int found; gid_t groupID; }; /* * This structure describes a specified login name * (from the -l logins option) */ struct reqlogin { char *loginname; struct reqlogin *next; int found; }; /* * These functions handle error and warning message writing. * (This deals with UNIX(r) standard message generation, so * the rest of the code doesn't have to.) * * Functions included: * initmsg Initialize the message handling functions. * wrtmsg Write the message using the standard message * generation facility. * * Static data included: * fcnlbl The label for standard messages * msgbuf A buffer to contain the edited message */ static char fcnlbl[MM_MXLABELLN+1]; /* Buffer for message label */ static char msgbuf[MM_MXTXTLN+1]; /* Buffer for message text */ /* * void initmsg(p) * * This function initializes the message handling functions. * * Arguments: * p A pointer to a character string that is the name of the * command, used to generate the label on messages. If this * string contains a slash ('/'), it only uses the characters * beyond the last slash in the string (this permits argv[0] * to be used). * * Returns: Void */ static void initmsg(char *p) /* Ptr to command name */ { /* Automatic data */ char *q; /* Local multi-use pointer */ /* Use only the simple filename if there is a slash in the name */ if ((q = strrchr(p, '/')) == NULL) q = p; else q++; /* Build the label for messages */ (void) snprintf(fcnlbl, sizeof (fcnlbl), "UX:%s", q); /* * Now that we've done all of that work, set things up so that * only the text-component of a message is printed. (This piece * of code will most probably go away in SVR4.1. */ (void) putenv("MSGVERB=text"); } /* * void wrtmsg(severity, action, tag, text[, txtarg1[, txtarg2[, ...]]]) * * This function writes a message using the standard message * generation facility. * * Arguments: * severity The severity-component of the message * action The action-string used to generate the action- * component of the message * tag Tag-component of the message * text The text-string used to generate the text-component * of the message * txtarg Arguments to be inserted into the "text" string using * vsnprintf() * * Returns: Void */ /* VARARGS4 */ static void wrtmsg(int severity, char *action, char *tag, char *text, ...) { /* Automatic data */ int errorflg; /* FLAG: True if error writing msg */ va_list argp; /* Pointer into vararg list */ errorflg = FALSE; /* Generate the error message */ va_start(argp, text); if (text != MM_NULLTXT) { /* LINTED */ errorflg = vsnprintf(msgbuf, sizeof (msgbuf), text, argp) > MM_MXTXTLN; } (void) fmtmsg(MM_PRINT, fcnlbl, severity, (text == MM_NULLTXT) ? MM_NULLTXT : msgbuf, action, tag); va_end(argp); /* * If there would have been a buffer overflow generating the error * message, the message will be truncated, so write a message and quit. */ if (errorflg) { (void) fmtmsg(MM_PRINT, fcnlbl, MM_WARNING, "Internal message buffer overflow", MM_NULLACT, MM_NULLTAG); exit(100); } } /* * These functions allocate space for the information we gather. * It works by having a memory heap with strings allocated from * the end of the heap and structures (aligned data) allocated * from the beginning of the heap. It begins with a 4k block of * memory then allocates memory in 4k chunks. These functions * should never fail. If they do, they report the problem and * exit with an exit code of 101. * * Functions contained: * allocblk Allocates a block of memory, aligned on a * 4-byte (double-word) boundary. * allocstr Allocates a block of memory with no particular * alignment * * Constant definitions: * ALLOCBLKSZ Size of a chunk of main memory allocated using * malloc() * * Static data: * nextblkaddr Address of the next available chunk of aligned * space in the heap * laststraddr Address of the last chunk of unaligned space * allocated from the heap * toomuchspace Message to write if someone attempts to allocate * too much space (>ALLOCBLKSZ bytes) * memallocdif Message to write if there is a problem allocating * main memory. */ #define ALLOCBLKSZ 4096 static char *nextblkaddr = NULL; static char *laststraddr = NULL; static char *memallocdif = "Memory allocation difficulty. Command terminates"; static char *toomuchspace = "Internal space allocation error. Command terminates"; /* * void *allocblk(size) * unsigned int size * * This function allocates a block of aligned (4-byte or double- * word boundary) memory from the program's heap. It returns a * pointer to that block of allocated memory. * * Arguments: * size Minimum number of bytes to allocate (will * round up to multiple of 4) * * Returns: void * * Pointer to the allocated block of memory */ static void * allocblk(unsigned int size) { /* Automatic data */ char *rtnval; /* Make sure the sizes are aligned correctly */ if ((size = size + (4 - (size % 4))) > ALLOCBLKSZ) { wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, toomuchspace); exit(101); } /* Set up the value we're going to return */ rtnval = nextblkaddr; /* Get the space we need off of the heap */ if ((nextblkaddr += size) >= laststraddr) { if ((rtnval = malloc(ALLOCBLKSZ)) == NULL) { wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, memallocdif); exit(101); } laststraddr = rtnval + ALLOCBLKSZ; nextblkaddr = rtnval + size; } /* We're through */ return ((void *)rtnval); } /* * char *allocstr(nbytes) * unsigned int nbytes * * This function allocates a block of unaligned memory from the * program's heap. It returns a pointer to that block of allocated * memory. * * Arguments: * nbytes Number of bytes to allocate * * Returns: char * * Pointer to the allocated block of memory */ static char * allocstr(unsigned int nchars) { if (nchars > ALLOCBLKSZ) { wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, toomuchspace); exit(101); } if ((laststraddr -= nchars) < nextblkaddr) { if ((nextblkaddr = malloc(ALLOCBLKSZ)) == NULL) { wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, memallocdif); exit(101); } laststraddr = nextblkaddr + ALLOCBLKSZ - nchars; } return (laststraddr); } /* * These functions control the group membership list, as found in the * /etc/group file. * * Functions included: * initmembers Initialize the membership list (to NULL) * addmember Adds a member to the membership list * isamember Looks for a particular login-ID in the list * of members * * Datatype Definitions: * struct grpmember Describes a group member * * Static Data: * membershead Pointer to the head of the list of group members */ struct grpmember { char *membername; struct grpmember *next; }; static struct grpmember *membershead; /* * void initmembers() * * This function initializes the list of members of specified groups. * * Arguments: None * * Returns: Void */ static void initmembers(void) { /* Set up the members list to be a null member's list */ membershead = NULL; } /* * void addmember(p) * char *p * * This function adds a member to the group member's list. The * group members list is a list of structures containing a pointer * to the member-name and a pointer to the next item in the structure. * The structure is not ordered in any particular way. * * Arguments: * p Pointer to the member name * * Returns: Void */ static void addmember(char *p) { /* Automatic data */ struct grpmember *new; /* Member being added */ new = (struct grpmember *)allocblk(sizeof (struct grpmember)); new->membername = strcpy(allocstr((unsigned int)strlen(p)+1), p); new->next = membershead; membershead = new; } /* * init isamember(p) * char *p * * This function examines the list of group-members for the string * referenced by 'p'. If 'p' is a member of the members list, the * function returns TRUE. Otherwise it returns FALSE. * * Arguments: * p Pointer to the name to search for. * * Returns: int * TRUE If 'p' is found in the members list, * FALSE otherwise */ static int isamember(char *p) { /* Automatic Data */ int found; /* FLAG: TRUE if login found */ struct grpmember *pmem; /* Pointer to group member */ /* Search the membership list for the 'p' */ found = FALSE; for (pmem = membershead; !found && pmem; pmem = pmem->next) { if (strcmp(p, pmem->membername) == 0) found = TRUE; } return (found); } /* * These functions handle the display list. The display list contains * all of the information we're to display. The list contains a pointer * to the login-name, a pointer to the free-field (comment), and a pointer * to the next item in the list. The list is ordered alphabetically * (ascending) on the login-name field. The list initially contains a * dummy field (to make insertion easier) that contains a login-name of "". * * Functions included: * initdisp Initializes the display list * adddisp Adds information to the display list * genreport Generates a report from the items in the display list * * Datatypes Defined: * struct display Describes the structure that contains the * information to be displayed. Includes pointers * to the login-ID, free-field (comment), and the * next structure in the list. * * Static Data: * displayhead Pointer to the head of the list of login-IDs to * be displayed. Initially references the null-item * on the head of the list. */ struct display { char *loginID; char *freefield; struct display *next; }; static struct display *displayhead; /* * void initdisp() * * Initializes the display list. An empty display list contains a * single element, the dummy element. * * Arguments: None * * Returns: Void */ static void initdisp(void) { displayhead = (struct display *)allocblk(sizeof (struct display)); displayhead->next = NULL; displayhead->loginID = ""; displayhead->freefield = ""; } /* * void adddisp(pwent) * struct passwd *pwent * * This function adds the appropriate information from the login * description referenced by 'pwent' to the list if information * to be displayed. It only adds the information if the login-ID * (user-name) is unique. It inserts the information in the list * in such a way that the list remains ordered alphabetically * (ascending) according to the login-ID (user-name). * * Arguments: * pwent Points to the (struct passwd) structure that * contains all of the login information on the * login being added to the list. The only * information that this function uses is the * login-ID (user-name) and the free-field * (comment field). * * Returns: Void */ static void adddisp(struct passwd *pwent) { /* Automatic data */ struct display *new; /* Display item being added */ struct display *prev; /* Previous display item */ struct display *current; /* Next display item */ int found; /* FLAG, insertion point found */ int compare = 1; /* strcmp() compare value */ /* Find where this value belongs in the list */ prev = displayhead; current = displayhead->next; found = FALSE; while (!found && current) { if ((compare = strcmp(current->loginID, pwent->pw_name)) >= 0) found = TRUE; else { prev = current; current = current->next; } } /* Insert this value in the list, only if it is unique though */ if (compare != 0) { /* * Build a display structure containing the value to add to * the list, and add to the list */ new = (struct display *)allocblk(sizeof (struct display)); new->loginID = strcpy(allocstr((unsigned int)strlen(pwent->pw_name)+1), pwent->pw_name); if (pwent->pw_comment && pwent->pw_comment[0] != '\0') new->freefield = strcpy(allocstr( (unsigned int)strlen(pwent->pw_comment)+1), pwent->pw_comment); else new->freefield = strcpy(allocstr( (unsigned int)strlen(pwent->pw_gecos)+1), pwent->pw_gecos); new->next = current; prev->next = new; } } /* * void genreport() * * This function generates a report on the standard output stream * (stdout) containing the login-IDs and the free-fields of the * logins that match the list criteria (-g and -l options) * * Arguments: None * * Returns: Void */ static void genreport(void) { /* Automatic data */ struct display *current; /* Value to display */ int i; /* Counter of characters */ /* * Initialization for loop. * (NOTE: The first element in the list of logins to display * is a dummy element.) */ current = displayhead; /* * Display elements in the list */ for (current = displayhead->next; current; current = current->next) { (void) fputs(current->loginID, stdout); for (i = LOGINFIELDSZ - strlen(current->loginID); --i >= 0; (void) putc(' ', stdout)) ; (void) fputs(current->freefield, stdout); (void) putc('\n', stdout); } } /* * listusers [-l logins] [-g groups] * * This command generates a list of user login-IDs. Specific login-IDs * can be listed, as can logins belonging in specific groups. * * -l logins specifies the login-IDs to display. "logins" is a * comma-list of login-IDs. * -g groups specifies the names of the groups to which a login-ID * must belong before it is included in the generated list. * "groups" is a comma-list of group names. * Exit Codes: * 0 All's well that ends well * 1 Usage error */ int main(int argc, char **argv) { /* Automatic data */ struct reqgrp *reqgrphead; /* Head of the req'd group list */ struct reqgrp *pgrp; /* Current item in the req'd group list */ struct reqgrp *qgrp; /* Prev item in the req'd group list */ struct reqgrp *rgrp; /* Running ptr for scanning group list */ struct reqlogin *reqloginhead; /* Head of req'd login list */ struct reqlogin *plogin; /* Current item in the req'd login list */ struct reqlogin *qlogin; /* Previous item in the req'd login list */ struct reqlogin *rlogin; /* Running ptr for scanning login list */ struct passwd *pwent; /* Ptr to an /etc/passwd entry */ struct group *grent; /* Ptr to an /etc/group entry */ char *token; /* Ptr to a token extracted by strtok() */ char **pp; /* Ptr to a member of a group */ char *g_arg = NULL; /* Ptr to the -g option's argument */ char *l_arg = NULL; /* Ptr to the -l option's argument */ int g_seen; /* FLAG, true if -g on cmd */ int l_seen; /* FLAG, TRUE if -l is on the command line */ int errflg; /* FLAG, TRUE if there is a command-line problem */ int done; /* FLAG, TRUE if the process (?) is complete */ int groupcount = 0; /* Number of groups specified by the user */ int rc; /* Return code from strcmp() */ int c; /* Character returned from getopt() */ /* Initializations */ initmsg(argv[0]); /* Command-line processing */ g_seen = FALSE; l_seen = FALSE; errflg = FALSE; opterr = 0; while (!errflg && ((c = getopt(argc, argv, "g:l:")) != EOF)) { /* Case on the option character */ switch (c) { case 'g': if (g_seen) errflg = TRUE; else { g_seen = TRUE; g_arg = optarg; } break; case 'l': if (l_seen) errflg = TRUE; else { l_seen = TRUE; l_arg = optarg; } break; default: errflg = TRUE; } } /* Write out a usage message if necessary and quit */ if (errflg || (optind != argc)) { wrtmsg(MM_ERROR, MM_NULLACT, MM_NULLTAG, USAGE_MSG); exit(1); } /* * If the -g groups option was on the command line, build a * list containing groups we're to list logins for. */ reqgrphead = NULL; if (g_seen) { /* Begin with an empty list */ groupcount = 0; /* Extract the first token putting an element on the list */ if ((token = strtok(g_arg, ",")) != NULL) { pgrp = (struct reqgrp *) allocblk(sizeof (struct reqgrp)); pgrp->groupname = token; pgrp->found = FALSE; pgrp->next = NULL; groupcount++; reqgrphead = pgrp; qgrp = pgrp; /* * Extract subsequent tokens (group names), avoiding * duplicate names (note, list is NOT empty) */ while ((token = strtok(NULL, ",")) != NULL) { /* Check for duplication */ rgrp = reqgrphead; rc = 0; while (rgrp && (rc = strcmp(token, rgrp->groupname))) rgrp = rgrp->next; if (rc != 0) { /* Not a duplicate. Add on the list */ pgrp = (struct reqgrp *) allocblk(sizeof (struct reqgrp)); pgrp->groupname = token; pgrp->found = FALSE; pgrp->next = NULL; groupcount++; qgrp->next = pgrp; qgrp = pgrp; } } } } /* * If -l logins is on the command line, build a list of logins * we're to generate reports for. * Begin with a null list. */ reqloginhead = NULL; if (l_seen) { /* Extract the first token from the argument to the -l option */ if ((token = strtok(l_arg, ",")) != NULL) { /* Put the first element in the list */ plogin = (struct reqlogin *) allocblk(sizeof (struct reqlogin)); plogin->loginname = token; plogin->found = FALSE; plogin->next = NULL; reqloginhead = plogin; qlogin = plogin; /* * For each subsequent token in the -l argument's * comma list ... */ while ((token = strtok(NULL, ",")) != NULL) { /* Check for duplication (list is not empty) */ rlogin = reqloginhead; rc = 0; while (rlogin && (rc = strcmp(token, rlogin->loginname))) rlogin = rlogin->next; /* * If it's not a duplicate, * add it to the list */ if (rc != 0) { plogin = (struct reqlogin *) allocblk(sizeof (struct reqlogin)); plogin->loginname = token; plogin->found = FALSE; plogin->next = NULL; qlogin->next = plogin; qlogin = plogin; } } } } /* * If the user requested that only logins be listed in that belong * to certain groups, compile a list of logins that belong in that * group. If the user also requested specific logins, that list * will be limited to those logins. */ /* Initialize the login list */ initmembers(); if (g_seen) { /* For each group in the /etc/group file ... */ while ((grent = getgrent()) != NULL) { /* For each group mentioned with the -g option ... */ for (pgrp = reqgrphead; (groupcount > 0) && pgrp; pgrp = pgrp->next) { if (pgrp->found == FALSE) { /* * If the mentioned group is found in * the /etc/group file ... */ if (strcmp(grent->gr_name, pgrp->groupname) == 0) { /* * Mark the entry is found, * remembering the group-ID * for later */ pgrp->found = TRUE; groupcount--; pgrp->groupID = grent->gr_gid; if (isausergroup(pgrp->groupID)) for (pp = grent->gr_mem; *pp; pp++) addmember(*pp); } } } } /* * If any groups weren't found, write a message * indicating such, then continue */ qgrp = NULL; for (pgrp = reqgrphead; pgrp; pgrp = pgrp->next) { if (!pgrp->found) { wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, "%s was not found", pgrp->groupname); if (!qgrp) reqgrphead = pgrp->next; else qgrp->next = pgrp->next; } else if (isasystemgroup(pgrp->groupID)) { wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, "%s is not a user group", pgrp->groupname); if (!qgrp) reqgrphead = pgrp->next; else qgrp->next = pgrp->next; } else qgrp = pgrp; } } /* Initialize the list of logins to display */ initdisp(); /* * Loop through the /etc/passwd file squirelling away the * information we need for the display. */ while ((pwent = getpwent()) != NULL) { /* * The login from /etc/passwd hasn't been included in * the display yet */ done = FALSE; /* * If the login was explicitly requested, include it in * the display if it is a user login */ if (l_seen) { for (plogin = reqloginhead; !done && plogin; plogin = plogin->next) { if (strcmp(pwent->pw_name, plogin->loginname) == 0) { plogin->found = TRUE; if (isauserlogin(pwent->pw_uid)) adddisp(pwent); else wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, "%s is not a user login", plogin->loginname); done = TRUE; } } } /* * If the login-ID isn't already on the list, if its primary * group-ID is one of those groups requested, or it is a member * of the groups requested, include it in the display if it is * a user login (uid >= 100). */ if (isauserlogin(pwent->pw_uid)) { if (!done && g_seen) { for (pgrp = reqgrphead; !done && pgrp; pgrp = pgrp->next) if (pwent->pw_gid == pgrp->groupID) { adddisp(pwent); done = TRUE; } if (!done && isamember(pwent->pw_name)) { adddisp(pwent); done = TRUE; } } /* * If neither -l nor -g is on the command-line and * the login-ID is a user login, include it in * the display. */ if (!l_seen && !g_seen) adddisp(pwent); } } /* Let the user know about logins they requested that don't exist */ if (l_seen) for (plogin = reqloginhead; plogin; plogin = plogin->next) if (!plogin->found) wrtmsg(MM_WARNING, MM_NULLACT, MM_NULLTAG, "%s was not found", plogin->loginname); /* * Generate a report from this display items we've squirreled away */ genreport(); /* * We're through! */ return (0); }